linux中記憶體洩漏的檢測(三)定製化的new/delete

懷想天空2015發表於2017-08-10

《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 functionvoid* __wrap_new(size_t)’:
cpptest.cpp:9:27: error: ‘__real_new’ was not declared in this scope
     return __real_new(size);
                           ^
cpptest.cpp: In functionvoid __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)申請陣列而釋放陣列第一項

改進

欲知如何解決,且看下回分解

相關文章