做b2b網(wǎng)站銷售怎樣讓客戶找上門如何創(chuàng)建一個(gè)網(wǎng)頁
? ? 在日常的開發(fā)中,內(nèi)存泄漏是一種比較比較棘手的問題,這是由于其具有隱蔽性,即使發(fā)生了泄漏,很難檢測到并且不好定位到哪里導(dǎo)致的泄漏。如果程序在運(yùn)行的過程中不斷出現(xiàn)內(nèi)存泄漏,那么越來越多的內(nèi)存得不到釋放,可用的內(nèi)存越來越小,最終導(dǎo)致系統(tǒng)無法正常運(yùn)行。
? ? 本文主要介紹一種能夠檢測內(nèi)存的方法,方便在日常的開發(fā)過程中排除程序是否存在內(nèi)存泄漏的情況。
? ? 內(nèi)存泄漏主要針對在堆區(qū)分配的內(nèi)存無法得到釋放,在堆區(qū)分配內(nèi)存的方法有malloc和new,對應(yīng)釋放內(nèi)存為free和delete。new和delete是針對C++的,本文主要監(jiān)控通過new分配的內(nèi)存的情況。
? ?new和delete是C++語言提供的運(yùn)算符,在程序可以對這兩個(gè)運(yùn)算符進(jìn)行重載,如下所示。
void * operator new(size_t size){void *ptr = malloc(size);LOGI("new size %d ptr %p ",size, ptr);return ptr;
}void * operator new[](size_t size){void *ptr = malloc(size);LOGI("new array size %d ptr %p ",size, ptr);return ptr;
}void operator delete(void *ptr) {LOGI("delete pointer %p",ptr);if(ptr == nullptr) return;free(ptr);
}
void operator delete[](void *ptr) {LOGI("delete array %p",ptr);if(ptr == nullptr) return;free(ptr);
}
? ? 上面重載了new、new[],delete和delete[]四個(gè)運(yùn)算符,為了驗(yàn)證正常使用new和delete操作能夠調(diào)用以上的運(yùn)算符,下面定義一個(gè)簡單的類
class MEM{
public:MEM(){LOGI("MEM constructor");}~MEM(){LOGI("MEM destructor");}
private:int a;
};
? ? 這里定義MEM類并在構(gòu)造函數(shù)和析構(gòu)函數(shù)加了打印,主要為了驗(yàn)證它們是否會被調(diào)用,下面開始使用new和delete申請和釋放內(nèi)存,如下所示。
LOGI("new int---------");
int *p1 = new int(3);
LOGI("new int array---------");
int *p2 = new int[5];
LOGI("new MEM object---------");
MEM * p3 = new MEM();
LOGI("new MEM object array---------");
MEM * p4 = new MEM[5];LOGI("delete p1---------");
delete p1;
LOGI("delete p2---------");
delete []p2;
LOGI("delete p3---------");
delete p3;
LOGI("delete p4---------");
delete []p4;
LOGI("---------");
上面的測試代碼流程如下:
new int(3) 請求分配一個(gè)整數(shù)
new int[5] 請求分配一個(gè)數(shù)組,大小為5
new MEM() 請求分配一個(gè)MEM類型的對象
new MEM[5] 請求分配一個(gè)MEM類型的數(shù)組,大小為5
最后調(diào)用delete分別釋放以上分配的內(nèi)存。運(yùn)行以上代碼,打印結(jié)果如下。
09:57:35.596 28994-29029 Native I new int---------
09:57:35.596 28994-29029 Native I new size 4 ptr 0xdc5191c0
09:57:35.596 28994-29029 Native I new int array---------
09:57:35.596 28994-29029 Native I new array size 20 ptr 0xdc50ed60
09:57:35.596 28994-29029 Native I new MEM object---------
09:57:35.596 28994-29029 Native I new size 4 ptr 0xdc5191c8
09:57:35.596 28994-29029 Native I MEM constructor
09:57:35.596 28994-29029 Native I new MEM object array---------
09:57:35.596 28994-29029 Native I new array size 24 ptr 0xdc50edc0
09:57:35.596 28994-29029 Native I MEM constructor
09:57:35.596 28994-29029 Native I MEM constructor
09:57:35.596 28994-29029 Native I delete p1---------
09:57:35.597 28994-29029 Native I delete pointer 0xdc5191c0
09:57:35.597 28994-29029 Native I delete p2---------
09:57:35.597 28994-29029 Native I delete array 0xdc50ed60
09:57:35.597 28994-29029 Native I delete p3---------
09:57:35.597 28994-29029 Native I MEM destructor
09:57:35.597 28994-29029 Native I delete pointer 0xdc5191c8
09:57:35.597 28994-29029 Native I delete p4---------
09:57:35.597 28994-29029 Native I MEM destructor
09:57:35.597 28994-29029 Native I MEM destructor
09:57:35.597 28994-29029 Native I delete array 0xdc50edc0
09:57:35.597 28994-29029 Native I ---------
? ? 通過以上log可以看到,重載的運(yùn)算符new、delete,構(gòu)造函數(shù)和析構(gòu)函數(shù)里都走進(jìn)去了,說明重載運(yùn)算符是可以接管分配和釋放內(nèi)存的工作的,而調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)還是由編譯器處理了,無須擔(dān)心創(chuàng)建對象和銷毀對象時(shí)這兩個(gè)函數(shù)沒有被調(diào)用。
? ? 盡管通過重載new和delete運(yùn)算符可以接管內(nèi)存的分配和釋放工作,但是在new操作符函數(shù)中還是無法指定是誰申請的內(nèi)存,為了能確定是哪里申請的內(nèi)存,需要對new操作符進(jìn)行改進(jìn),如下所示。
void * operator new(size_t size,const char * file, size_t line){LOGI("new size %d file: %s line %d",size, file, line);void *ptr = malloc(size);return ptr;
}void * operator new[](size_t size,const char * file, size_t line){LOGI("new array size %d file: %s line %d",size, file, line);void *ptr = malloc(size);return ptr;
}#define new new(__FILE__,__LINE__)
? ? 上面重新定義了new操作符,加入了文件命和行號,并且把new定義為一個(gè)宏,調(diào)用new時(shí)自動(dòng)加入文件名宏和行號宏,這樣在代碼中調(diào)用new申請內(nèi)存時(shí)自動(dòng)帶上對應(yīng)的文件名和行號。有了文件名和行號就能知道哪個(gè)地方申請的內(nèi)存。
? ? 為了統(tǒng)計(jì)當(dāng)前系統(tǒng)內(nèi)存的使用請求,接下來要把內(nèi)存申請的記錄保存起來,這里使用一個(gè)單鏈表對內(nèi)存的申請信息進(jìn)行保存,鏈表的元素使用Node表示,代碼如下。
typedef struct Node{void *ptr;size_t size;char *file;size_t line;struct Node *next;
} Node;Node *head = nullptr;
void addRecord(void *ptr, size_t size, const char *file, size_t line){LOGI("addRecord");Node * node = (Node *)malloc(sizeof(Node));node->ptr = ptr;node->size = size;node->file = (char *)malloc(strlen(file)+1);strcpy(node->file,file);node->line = line;node->next= nullptr;if(head == nullptr){head = node;} else{node->next = head;head = node;}
}
void removeRecord(void *ptr){if(head == nullptr) return;Node *p = head;if(p->ptr == ptr){head = head->next;if(p->file != nullptr){free(p->file);}free(p);return;}Node * q = p->next;while (q != nullptr){if(q->ptr == ptr){p->next = q->next;if(q->file != nullptr){free(q->file);}free(q);return;}p = q;q = q->next;}
}void * operator new(size_t size,const char * file, size_t line){LOGI("new size %d file: %s line %d",size, file, line);void *ptr = malloc(size);if(ptr != nullptr){addRecord(ptr, size, file, line);}return ptr;
}void * operator new[](size_t size,const char * file, size_t line){LOGI("new array size %d file: %s line %d",size, file, line);void *ptr = malloc(size);if(ptr != nullptr){addRecord(ptr, size, file, line);}return ptr;
}void operator delete(void *ptr) {if(ptr == nullptr) return;removeRecord(ptr);free(ptr);
}
void operator delete[](void *ptr) {if(ptr == nullptr) return;removeRecord(ptr);free(ptr);
}#define new new(__FILE__,__LINE__)
? ? 鏈表元素使用Node表示,Node包含了申請內(nèi)存的地址,大小、文件名、行號以及下一個(gè)Node的地址。
? ?head表示鏈表頭。
? ? addRecord向鏈表添加記錄
? ? removeRecord根據(jù)指針從鏈表中釋放對應(yīng)的Node。
? ?new運(yùn)算符申請內(nèi)存后向鏈表添加記錄,delete運(yùn)算符從鏈表刪除記錄后再釋放內(nèi)存。
? ? ? 有了保存內(nèi)存信息的聊吧,可以統(tǒng)計(jì)當(dāng)前內(nèi)存的使用請求,下面實(shí)現(xiàn)統(tǒng)計(jì)當(dāng)前內(nèi)存使用情況的快照。
int showSnapshot(){LOGI("========Memory Snapshot Begin=========");int total = 0;Node *p = head;while (p != nullptr){total += p->size;LOGI("file %s line %d allocate size %d", p->file,p->line, p->size);p = p->next;}LOGI("total memory allocate is %d", total);LOGI("========Memory Snapshot End=========");return total;
}
? ? ?在showSnapshot中,先遍歷鏈表打印當(dāng)前內(nèi)存的信息,最后打印當(dāng)前申請的總的內(nèi)存。下面再來打印上面的測試代碼的內(nèi)存快照,代碼如下。
int *p1 = new int(3);int *p2 = new int[5];MEM * p3 = new MEM();MEM * p4 = new MEM[5];showSnapshot();delete p1;delete []p2;delete p3;delete []p4;
? ? 在申請完所有的內(nèi)存后,調(diào)用showSnapshot打印當(dāng)前內(nèi)存的申請情況,如下所示。
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I ========Memory Snapshot Begin=========
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I file D:/samples/Demos/AndroidSamples/Memroy/app/src/main/cpp/main.cpp line 213 allocate size 24
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I file D:/samples/Demos/AndroidSamples/Memroy/app/src/main/cpp/main.cpp line 212 allocate size 4
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I file D:/samples/Demos/AndroidSamples/Memroy/app/src/main/cpp/main.cpp line 211 allocate size 20
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I file D:/samples/Demos/AndroidSamples/Memroy/app/src/main/cpp/main.cpp line 210 allocate size 4
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I total memory allocate is 52
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I ========Memory Snapshot End=========
? ? ?從以上的快照可以看到當(dāng)前內(nèi)存的申請情況,通過這些信息可以排查某個(gè)文件的某一行申請的內(nèi)存是否應(yīng)該釋放調(diào),由此可以判斷是否出現(xiàn)內(nèi)存泄漏的情況。
? ?在平常的開發(fā)中,盡可能使用智能指針,減少顯示通過new申請內(nèi)存的情況,這樣也可以避免內(nèi)存泄漏。
本示例的工程已上傳到github,鏈接為示例工程地址