最近想轉到Linux下的C\C++編程,但是當前工作還是以在Window下的C++開發為主,偶爾也在Linux和Mac OS開發一些小工具,但是就是開發這些小工具讓我有想轉到Linux下C\C++編程的念頭。但是工作還是要做的,不然沒得飯吃啊,所以就只能利用下班時間及周末時間研究一下,先熟悉熟悉,由於大學木有認真的學習過C語言,一直以為C語言里有bool類型,直到工作后才知道C語言中木有這個類型,o(╯□╰)o。。。,所以就從C語言學起,我已經學過C++了,為什么還要從頭來學C語言呢,我從事C++開發也就一年多一點,C++博大精深,所以我連半個C++程序員都算不上。廢話少說了,為將來轉到Linux下進行C\C++編程做准備,我得重頭開始。
但是要怎么自學才能達到我的目的呢,光看課本可不行,不久就會忘了,我想看別人開源項目應該是一個不錯的選擇。所以,我計划是這樣的:在codeproject找一些開源項目看,無論該項目代碼量是多少,就算是幾十行,我想也有我們值得學習的地方,畢竟代碼上傳到codeproject讓People mountain people sea的開發者檢閱,質量也不會差到哪里去。
好了,就不多說,讓我們從一個非常簡單的項目說起,其實也不能說是項目,更確切的說是一個Demo。
該項目是一個C語言的內存檢測小程序,一個印度人寫的,網址如下:http://www.codeproject.com/Articles/19361/Memory-Leak-Detection-in-C。
在C語言中,我們一般都是用malloc和calloc獲取堆上的內存,用free釋放內存。有時,我們可能忘了用free釋放我們用malloc或者calloc獲取的內存,這就導致內存泄露。而這個小程序就非常容易的檢測出我們的程序到底哪里出現了內存泄露,我認為這在debug模式下還是有用的。好,現在讓我們來看看這個小程序是怎么樣實現的。
這個小程序主要包含兩個文件一個頭文件leak_detector_c.h和一個源文件leak_detector_c.c。該程序把庫函數malloc、calloc和free函數進行了包裝,分別包裝成xmalloc、xcalloc和xfree函數。當客戶調用malloc、calloc函數分配內存時,實際是調用程序自定義的函數xmalloc、xcalloc,同理,free也一樣。當調用malloc分配內存時(實際是調用xmalloc,xmalloc里再調用malloc),用一個隊列記錄分配的內存地址、內存大小、所在文件以及行數(該隊列用鏈表實現,待會看代碼就知道)。calloc as well as malloc。每當free函數調用時,就從該隊列移除這個地址信息,所以如果程序執行完畢之后,如果沒有free所有的內存的話,該隊列就不為空。但是我們如何在程序執行完畢之后得到內存泄露的信息?在C語言中,我們有這樣一個函數atexit(),該函數的作用是注冊終止函數(即main執行結束后調用的函數)。OK,現在該小程序原理搞懂了,看代碼就more easy 了。
leak_detector_c.h如下,詳細的說明已在注釋里面了。(其實我覺得沒必要有注釋,因為這個小程序是在是太簡單了,懂點C語言的都會看明白)
#define FILE_NAME_LENGTH 256 #define OUTPUT_FILE "/home/leak_info.txt" //存放內存泄露的信息 #define malloc(size) xmalloc (size, __FILE__, __LINE__) //重新實現malloc、calloc和free #define calloc(elements, size) xcalloc (elements, size, __FILE__, __LINE__) #define free(mem_ref) xfree(mem_ref) struct _MEM_INFO //一次分配的內存信息 { void *address; //分配的地址 unsigned int size; //地址大小 char file_name[FILE_NAME_LENGTH]; //在哪個文件申請 unsigned int line; //行數 }; typedef struct _MEM_INFO MEM_INFO; struct _MEM_LEAK { //將所有malloc和calloc申請的內存串起來 MEM_INFO mem_info; struct _MEM_LEAK * next; }; typedef struct _MEM_LEAK MEM_LEAK; void add(MEM_INFO alloc_info); void erase(unsigned pos); void clear(void); void * xmalloc(unsigned int size, const char * file, unsigned int line); void * xcalloc(unsigned int elements, unsigned int size, const char * file, unsigned int line); void xfree(void * mem_ref); void add_mem_info (void * mem_ref, unsigned int size, const char * file, unsigned int line); void remove_mem_info (void * mem_ref); void report_mem_leak(void); //在atexit注冊的終止函數
leak_detector_c.c文件如下:
static MEM_LEAK * ptr_start = NULL; //用這兩個指針將malloc和calloc申請的內存串起來,只能本文件中使用 static MEM_LEAK * ptr_next = NULL; // 向隊列中添加申請的內存信息 void add(MEM_INFO alloc_info) { MEM_LEAK * mem_leak_info = NULL; mem_leak_info = (MEM_LEAK *) malloc (sizeof(MEM_LEAK)); mem_leak_info->mem_info.address = alloc_info.address; mem_leak_info->mem_info.size = alloc_info.size; strcpy(mem_leak_info->mem_info.file_name, alloc_info.file_name); mem_leak_info->mem_info.line = alloc_info.line; mem_leak_info->next = NULL; if (ptr_start == NULL) { ptr_start = mem_leak_info; ptr_next = ptr_start; } else { ptr_next->next = mem_leak_info; ptr_next = ptr_next->next; } } //從隊列中移除內存信息 void erase(unsigned pos) { unsigned index = 0; MEM_LEAK * alloc_info, * temp; if(pos == 0) { MEM_LEAK * temp = ptr_start; ptr_start = ptr_start->next; free(temp); } else { for(index = 0, alloc_info = ptr_start; index < pos; alloc_info = alloc_info->next, ++index) { if(pos == index + 1) { temp = alloc_info->next; alloc_info->next = temp->next; free(temp); break; } } } } //從隊列中移除所有的內存信息 void clear() { MEM_LEAK * temp = ptr_start; MEM_LEAK * alloc_info = ptr_start; while(alloc_info != NULL) { alloc_info = alloc_info->next; free(temp); temp = alloc_info; } } //對malloc的包裝 void * xmalloc (unsigned int size, const char * file, unsigned int line) { void * ptr = malloc (size); if (ptr != NULL) { add_mem_info(ptr, size, file, line); } return ptr; } //對calloc的包裝 void * xcalloc (unsigned int elements, unsigned int size, const char * file, unsigned int line) { unsigned total_size; void * ptr = calloc(elements , size); if(ptr != NULL) { total_size = elements * size; add_mem_info (ptr, total_size, file, line); } return ptr; } //對free的包裝 void xfree(void * mem_ref) { remove_mem_info(mem_ref); free(mem_ref); } //獲取申請分配的內存信息,並將它添加到隊列中 void add_mem_info (void * mem_ref, unsigned int size, const char * file, unsigned int line) { MEM_INFO mem_alloc_info; memset( &mem_alloc_info, 0, sizeof ( mem_alloc_info ) ); mem_alloc_info.address = mem_ref; mem_alloc_info.size = size; strncpy(mem_alloc_info.file_name, file, FILE_NAME_LENGTH); mem_alloc_info.line = line; add(mem_alloc_info); } //從隊列中移除已分配的內存信息 void remove_mem_info (void * mem_ref) { unsigned short index; MEM_LEAK * leak_info = ptr_start; for(index = 0; leak_info != NULL; ++index, leak_info = leak_info->next) { if ( leak_info->mem_info.address == mem_ref ) { erase ( index ); break; } } } //回調函數,向文件中寫入內存泄露的情況 void report_mem_leak(void) { unsigned short index; MEM_LEAK * leak_info; FILE * fp_write = fopen (OUTPUT_FILE, "wt"); char info[1024]; if(fp_write != NULL) { sprintf(info, "%s\n", "Memory Leak Summary"); fwrite(info, (strlen(info) + 1) , 1, fp_write); sprintf(info, "%s\n", "-----------------------------------"); fwrite(info, (strlen(info) + 1) , 1, fp_write); for(leak_info = ptr_start; leak_info != NULL; leak_info = leak_info->next) { sprintf(info, "address : %d\n", leak_info->mem_info.address); fwrite(info, (strlen(info) + 1) , 1, fp_write); sprintf(info, "size : %d bytes\n", leak_info->mem_info.size); fwrite(info, (strlen(info) + 1) , 1, fp_write); sprintf(info, "file : %s\n", leak_info->mem_info.file_name); fwrite(info, (strlen(info) + 1) , 1, fp_write); sprintf(info, "line : %d\n", leak_info->mem_info.line); fwrite(info, (strlen(info) + 1) , 1, fp_write); sprintf(info, "%s\n", "-----------------------------------"); fwrite(info, (strlen(info) + 1) , 1, fp_write); } } clear(); }
下面我們用一個簡單例子測一下:
#include <malloc.h> #include "leak_detector_c.h" int main() { char * ptr1 = (char *)malloc(10); int * ptr2 = (int *)calloc(10, sizeof(int)); float * ptr3 = (float *) calloc(15, sizeof(float)); free(ptr2); atexit(report_mem_leak); return 0; }
用gcc編譯,運行之后,就可以在/home目錄下找到這樣一個文件leak_info.txt,該文件就記錄了我們內存泄露的情況。是不是很easy。。。親。。。。
OK,從這個小程序中,我學到兩點知識:
1、鏈表操作
2、用atexit函數注冊終止回調函數
ye,ye,That's all。This is enough。不是嗎。。。