摘要:介紹一個動態記憶體管理模組,可以有效地檢測C程式中記憶體流失和寫記憶體越界等錯誤,適用於具有標準C語言開發環境的各種平台。
關鍵詞:C語言 動態記憶體 記憶體流失 寫越界
引言
當前,絕大多數嵌入式平台上的軟體都採用C語言編寫。除了代碼簡潔、運行高效之外,靈活操作記憶體的能力更是C語言的重要特色。然而,不恰當的記憶體操作通常 也是錯誤的根源之一。如“記憶體流失” ——不能正確地釋放已指派的動態記憶體,就是一種非常難於檢測的存錯誤。持續的記憶體流失會使程式效能下降到最終完全不能運行,進而影響到所有其它有動態記憶體 需求的程式,在某些相對簡單的嵌入式平台上甚至會妨礙作業系統的運轉。再如“寫記憶體越界”,一種不合法的寫記憶體操作,極可能破壞到本程式中正在使用的其它 資料,嚴重的時候還可能對其它正在啟動並執行程式甚至整個系統造成影響。為此,本文介紹一個增強、可定製的動態記憶體管理模組(以下不妨簡稱Fense),在 C語言提供的記憶體配置函數基礎上,增加了對動態記憶體的管理功能;能記錄軟體運行過程中出現的記憶體流失資訊,同時也具一定的監測記憶體操作的能力;可以發現絕 大多數對動態記憶體的寫越界錯誤。
1 Fense的設計原理
Fense通過設立一個雙向鏈表(struct Head *stHead)來儲存所有被分配的動態記憶體塊的資訊。鏈表中的每個節點對應一個動態記憶體塊,節點中包括此記憶體大小、分配發生時所在的源檔案名稱和行號以及 被釋放的時候,Fense又從st_Head中刪除之,檢查st_Head中的節點即可得到未被釋放的本節點的數值校正和等。Fense將每一個分配的動 態記憶體塊插入到鏈表st_Head中;當此記憶體放記憶體塊資訊。鏈表節點結構定義如下:
CODE:struct Head{
char file; /分配所在源檔案名稱*/
unsigned long line; /*分配所在的行號*/
size_t size; /*分配的記憶體大小*/
int checksum; /*鏈表節點校正和*/
struct Head prev,next; /*雙鏈表的前後節點指標*/
};
/*全域的雙向鏈表*/
struct Head *st_Head=NULL;
為了檢測寫越界的錯誤,Fense在使用者申請的記憶體前後各增加了一定大小的記憶體作為監測地區,並初始化成預定值。這樣,當程式發生越界寫操作時,預定值就會發生改變,Fense即可檢測到錯誤。
通過Fense分配到的動態記憶體結構1所示。由此可知,Fense_Malloc(Fense的記憶體配置函數)返回給使用者的指標ptr指向的是使用者申請記憶體地區的起始位置。鏈表節點、前/後監測地區均為Fense內部使用,是使用者不可見的。
2 使用者定製選項
Fense有5組宏定義提供給使用者對功能進行定製。各組選項控制意義如下:
WARN_ON_ZERO_MALLOC 使用者申請零分配空間時警告資訊。
FILL_ON_MALLOC 分配時初始化記憶體塊
FILL_ON_MALLOC_VAL 分配初始化時的預設值
FILL_ON_FREE 釋放時填充記憶體塊
FILL_ON_FREE_VAL 釋放時填充記憶體塊的預設值
以上4個選項的主要功能是初始化剛分配到的記憶體和剛被釋放的記憶體為預設值,儘可能地避免出現因使用未初始經的記憶體而引發的錯誤。
FENSE_FRONT_SIZE 定義前監測地區大小
FENSE_FRONT_VAL 定義前監測地區的預設值
FENSE_END_SIZE 定義後監測地區大小
FENSE_END_VAL 定義後監測工域的預設值
在Fense工作過程中,對記憶體越界寫操作的檢驗是通過比較監測地區的當前值與本監測地區的預設值來確定的。顯然不能排除這樣一種可能:即發生在監測地區 的越界寫操作寫入的數值與監測地區的預設值恰好相同,此時,Fense無法發現錯誤的發生。對於這種情況,使用者可以通過更改監測地區預設值 (FENSE_FRONT_VAL和FENSE_END_VAL)和監測地區大小(FENSE_FRONT_SIZE和FENSE_END_SIZE)為 多組不同的值來反覆測試,這樣就可以大幅度地提高監測的準確性。
VALIDATE_FREE
free是檢查本記憶體塊是否在鏈表中
CHECK_ALL_MEMORY_ON_FREE
free時檢查鏈表中的所有記憶體塊
由於存在這樣一種情況:對記憶體塊A的寫操作出現了越界錯誤,寫到了另一記憶體塊B的地區內。此時,僅僅檢查記憶體塊A的有效性就無法發現問題,如果同時檢查所有的動態記憶體塊,則有可能發現錯誤所在。以上選項即為此而設。
FENSE_LOCK 擷取對鏈表st_Head的操作權
FENSE_UNLOCK 釋放對鏈表st_Head的操作權
考慮到的在多線程環境中,可能有多個線程同時用Fense進行記憶體管理,而Fense使用的鏈表st_Head是全域變數,因此提供了以上2個宏來實現對 st_Head的互斥訪問。宏的具體定義依賴於使用者所在的軟體環境,使用者可自行實現。對於單線程系統,僅需將這2個宏定義為空白即可。
為便於使用,Fense的標頭檔中還包括了以下定義,使得使用者基本不用改動現有的原始碼就可引入Fense。
CODE:#define malloc(size) Fense_Malloc(size,_FILE_,_LINE_)
#define free(ptr) Fense_Free(ptr,_FILE_,_LINE_)
#define realloc(ptr,new_size) Fense_Realloc(ptr,new_size,_FILE_,_LINE_)
#define colloc(num,size) Fense_Calloc(num,size,_FILE_,_LINE_)
3 運行時控制
Fense監測記憶體的功能可以在運行動態地開關。此功能通過將全域變數st_Disbaled賦值為零或非零來實現。在調試過程中,可以在調試器中即時修 改st_Disabled的值來控制Fense的行為,省去了重編譯原始碼的需要。對於那些需要大量編譯時間的大型工程或交叉平台開發的軟體項目來說,這 是非常有利的。
4 Fense的具體實現
Fense提供Fense_Malloc、Fense_Free、Fense_Realloc及Fense_Calloc等記憶體管理函數,功能和調用形式 與C語言中的malloc、free、realloc和calloc保持一致。限於篇幅,這裡僅對Fense_Malloc和Fense_Free的實現 過程做一個簡單描述
CODE:/*記憶體配置函數*/
void *Fense_Malloc(size_t size,char *file,unsigned long line)
{
//檢查Fense的運行時開關,如果Fense被關閉,則調用malloc
//分配並返回
//檢查是否零分配,如有則提示警告資訊後返回0(使用者定製選項)
//分配記憶體,包括鏈表節點地區和前/後監測地區
//初始化鏈表節點,儲存分配記憶體的資訊,包括分配的大小、所在檔案名稱和行號
//將此節點插入鏈表st_Head
//為本節點地區計算校正和
//用預設值初始化前/後監測地區
//用預設值填充使用者記憶體地區(使用者定製選項)
//返回使用者記憶體地區的起始位置
}
/*記憶體釋放函數*/
void Fense_Free(void *uptr,char *file,unsigned long line)
{
//檢查Fense的運行時開關,如果Fense初關閉,則調用free釋譯並返回
//檢查所有Fense管理下的動態記憶體(使用者定製選項)
//判斷當前記憶體塊是否在鏈表st_Head中,如果不在則提示
//警靠資訊,退出(使用者定製選項)
//檢查當前記憶體塊是否存在越界操作
//將當前記憶體塊的相應的鏈表節點從st_Head中刪除
//重新計算當前節點的前後相鄰節點的校正和
//用預設值填充被釋放的記憶體區(使用者定製選項)
//調用free釋放當前的記憶體塊
}
結束語
作為對C程式運行時的記憶體錯誤進行監測的代碼模組,Fense能發現幾乎所有的記憶體流失和絕大多數的越界操作,並儘可能地記錄了改正程式錯誤所需要的資訊;有效地減少了程式設計人員的調試時間,在實際嵌入式產品開發中取得了很好的效果。
上一篇:《Linux下PCI裝置驅動程式開發 --- PCI驅動程式實現(三)》
下一篇:《用I/O命令訪問PCI匯流排裝置配置空間》