linux中記憶體洩漏的檢測(三)定製化的new/delete
《linux中記憶體洩漏的檢測(二)定製化的malloc/free》中的__wrap方法只解決了C的問題,這一節介紹怎麼讓C++中的new/delete也能方便地插入計數程式碼。
wrap方法嘗試
可不可以使用__wrap_new/__wrap_delete
?我們試試看。
我寫了這樣的測試程式碼
#include <iostream>
using namespace std;
int count = 0;
void * __wrap_new(size_t size)
{
count++;
return __real_new(size);
}
void __wrap_delete(void *ptr)
{
count--;
__real_delete(ptr);
}
int main()
{
count = 0;
int *p1 = new int;
int *p2 = new int;
delete p1;
if(count != 0)
cout<<"memory leak!"<<endl;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
然後這樣編譯,g++ -o test test.cpp -Wl,--wrap,new -Wl,--wrap,delete
,結果
cpptest.cpp: In function ‘void* __wrap_new(size_t)’:
cpptest.cpp:9:27: error: ‘__real_new’ was not declared in this scope
return __real_new(size);
^
cpptest.cpp: In function ‘void __wrap_delete (void*)’:
cpptest.cpp:15:22: error: ‘__real_delete’ was not declared in this scope
__real_delete(ptr);
^
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
看來這種方法不可行,這要從new和malloc的區別說起。
new VS. malloc
malloc很好理解,它的作用就是分配一段指定大小的記憶體空間。
而new的工作分為兩步:
第一步也是分配一段指定大小的記憶體空間,這一步與malloc相同,它有一個專用的名字,叫operator new
第二步是將分配到的記憶體以指定的方式初始化化,這是malloc所沒有的,它也有一個專用的名字,叫placement new
步驟 | 作用 | 與malloc的關係 | 是否可以過載 | 怎樣使用 |
---|---|---|---|---|
operator new | 分配一段指定大小的空間 | 相當於malloc | 可以過載 |
可以單獨呼叫,如class *pA = operator new(100) ,相當於class
*pA = malloc(100); |
placement new | 將一段空間以指定的方式初始化 | malloc不能提供這樣的功能 | 不能過載 |
可以把空間的指標作為引數傳入,單獨呼叫這一行為執行初始化操作,如class *pA = new(buf) class(); ,相當於使用class::class()初始化buf這段記憶體 |
關於operator new和placement new和更多細節,可以參考更多文章,但顯然new的功能非常複雜,並不是一個__wrap_new(size_t size)
能解決的。
operator new 過載
new的功能雖然複雜,但我們所關心的只是其中與分配記憶體相關的部分,也就是operator new。幸好,它可以過載。
C++支援過載,我們可以過載new中的operater new,在其中加入計數功能,並通過malloc實現記憶體申請。
#include <iostream>
using namespace std;
#include <stdio.h>
#include <stdlib.h>
int count = 0;
void * operator new(size_t size)
{
count++;
return malloc(size);
}
void operator delete(void *ptr)
{
count--;
free(ptr);
}
int main()
{
count = 0;
int *p1 = new int;
int *p2 = new int;
delete p1;
if(count != 0)
cout<<"memory leak!"<<endl;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
既然new也是通過呼叫malloc實現的,那麼也不用operator new和malloc分別統計了,只需要統計malloc就行了。因為__wrap_symbol
和__real_symbol
都是C函式,所有要使用extern
"C"
。
#include <iostream>
using namespace std;
#include <stdio.h>
#include <stdlib.h>
int count = 0;
extern "C"
{
void* __real_malloc(int c);
void * __wrap_malloc(int size)
{
count++;
return __real_malloc(size);
}
void __real_free(void *ptr);
void __wrap_free(void *ptr)
{
count--;
__real_free(ptr);
}
}
void * operator new(size_t size)
{
return malloc(size);
}
void operator delete(void *ptr)
{
free(ptr);
}
int main()
{
count = 0;
int *p1 = new int(3);
int *p2 = new int(4);
cout<<*p1<<' '<<*p2<<endl;
delete p1;
if(count != 0)
cout<<"memory leak!"<<endl;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
分析
- 優點
(1)使用方便 — 不需要改產品程式碼,只需要修改編譯選項即可完成。
(2)範圍全面 — wrap是個連結選項,對所有通過__wrap_malloc和__wrap_free連結到一起的檔案都起作用,不論是靜態庫還是動態庫。
(3)c的檢測與c++的檢測無縫相容
- 缺點
(1)該方法要求執行結束時對執行中產生的列印分析才能知道結果。
(2)只能檢測是否洩漏,卻沒有具體資訊,比如洩漏了多少空間
(3)不能說明是哪一行程式碼引起了洩漏
(4)這一方法雖然解決了C++的替換問題,卻引入了新的問題。因為在C++中對於同一指標申請和釋放,申請和釋放的大小卻有可能不相等,導致有些情況的記憶體洩漏檢測不到。比如(a)申請子類而析構父類(b)申請陣列而釋放陣列第一項
改進
欲知如何解決,且看下回分解