1. 程式人生 > >C++ new和delete的原理分析

C++ new和delete的原理分析

前言

Effective C++ rule 16規則,讓我重新認識了delete與new在處理陣列時候的方式。new 有兩種形式的new,一種是生成一個物件的operator New,另一個是用於陣列的operator new []。同時 operator delete也分普通版本的operator delete 以及陣列版的operator delete[].

先說結論系列

1.Operator new[]的工作原理是會使用malloc函式一次性申請好此次操作+4個位元組大小的記憶體空間,其所申請的記憶體結構如下:

陣列元素個數n 物件1 物件2 物件3· ······ 物件n-1 物件n

圖1 operator new[]返回值的記憶體佈局

然後依據本次申請的物件數,迴圈呼叫陣列內各個物件的建構函式。

2.Operator new 的工作原理沒有什麼好說的,就是malloc物件所需大小的記憶體,然後呼叫物件的建構函式。它並沒有在記憶體中記錄陣列的物件個數。

3.delete []的工作原理是先把引數向後偏移4個位元組,得到後續元素個數,然後在一個迴圈中依次呼叫各元素的解構函式,最後再呼叫free()函式,其中傳遞給free的是偏移之後的地址。

4.delete 的工作原理很簡單,呼叫引數的解構函式,然後在把這個地址無需偏移傳遞給free.

5.operator new [] 與delete[]成對出現;operator new 與delete成對出現。

Talk is cheap, show my code.

#include <stdio.h>
#include <stdlib.h>
#include <memory>
using namespace std;
class Super
{
public:
    Super()
    {
        printf("%d Super()\n",this);
        c = (int)this;
    }
    ~Super()
    {
        printf
("%d ~Super\n", this); } public: int c; }; int main() { Super * sp = new Super[15]; Super * sp1 = new Super; //printf("%d\n", *((char*)((int)sp1-4))); delete[] sp; delete sp1; //delete sp1; system("pause"); return 0; }

我們希望看到有15次 Super建構函式的呼叫,以及15次解構函式的呼叫。
執行結果:

6374692 Super()
6374696 Super()
6374700 Super()
6374704 Super()
6374708 Super()
6374712 Super()
6374716 Super()
6374720 Super()
6374724 Super()
6374728 Super()
6374732 Super()
6374736 Super()
6374740 Super()
6374744 Super()
6374748 Super()
6380784 Super()
6374748 ~Super
6374744 ~Super
6374740 ~Super
6374736 ~Super
6374732 ~Super
6374728 ~Super
6374724 ~Super
6374720 ~Super
6374716 ~Super
6374712 ~Super
6374708 ~Super
6374704 ~Super
6374700 ~Super
6374696 ~Super
6374692 ~Super
6380784 ~Super

有15次建構函式的呼叫,15次解構函式的呼叫。這次正常的,我們所想要的。
看彙編程式碼:
從Super *sp =new Super[15]開始,我們看彙編程式碼:

00BB261D  push        40h  將40H作為引數壓入棧,注意40H=64=15*4+4.
00BB261F  call        operator new[] (0BB10C8h)  首先會呼叫operator new []函式,引數是40H.
    我們看這個 operator new []函式:
operator new[]:
00BB10C8  jmp         operator new[] (0BB28D0h)跳轉到真正的入口 
     4: void *__CRTDECL operator new[](size_t count) _THROW1(std::bad_alloc)
     5:     {   // try to allocate count bytes for an array
00BB28D0  push        ebp  
00BB28D1  mov         ebp,esp  
     6:     return (operator new(count));
00BB28D3  mov         eax,dword ptr [count]  
00BB28D6  push        eax  
00BB28D7  call        operator new (0BB11BDh)  呼叫operaotr new ,引數是eax=40H
    我們再看0B11BDH處的operator new ,可以發現那就是類似於malloc的過程。
00BB28DC  add         esp,4  
     7:     }
00BB2624  add         esp,4  
00BB2627  mov         dword ptr [ebp-11Ch],eax  eax是operator new []的返回值
00BB262D  mov         dword ptr [ebp-4],0  
00BB2634  cmp         dword ptr [ebp-11Ch],0  比較是否為NULL,以判斷申請記憶體是否成功
00BB263B  je          main+97h (0BB2677h)  
00BB263D  mov         eax,dword ptr [ebp-11Ch]  
00BB2643  mov         dword ptr [eax],0Fh  往0BB28D0H的operator new[]返回值所在的記憶體寫入0FH(15).而eax其實就是malloc的返回值, 是申請記憶體塊的起始處。首先往記憶體塊的起始處寫入後面緊跟的元素的個數。這個起始的4個位元組就是圖1 最左側的“陣列元素個數”
00BB2649  push        0BB1087h  將~Super()的地址壓棧
00BB264E  push        0BB1023h  將Super()的地址壓棧
00BB2653  push        0Fh      將元素個數壓棧
00BB2655  push        4            將每個元素的大小壓棧
00BB2657  mov         ecx,dword ptr [ebp-11Ch]  
00BB265D  add         ecx,4  
00BB2660  push        ecx      將malloc返回值往高地址偏移4個位元組入棧,這其實就是第一人陣列元素的地址 
00BB2661  call        `eh vector constructor iterator' (0BB1145h)  呼叫迭代構造器 
再看迭代構造器:
`eh vector constructor iterator':
00BB28F0  push        ebp  
00BB28F1  mov         ebp,esp  
00BB28F3  push        0FFFFFFFEh  
00BB28F5  push        0BBA200h  
00BB28FA  push        0BB108Ch  
00BB28FF  mov         eax,dword ptr fs:[00000000h]  
00BB2905  push        eax  
00BB2906  add         esp,0FFFFFFF0h  
00BB2909  push        ebx  
00BB290A  push        esi  
00BB290B  push        edi  
00BB290C  mov         eax,dword ptr ds:[00BBB000h]  
00BB2911  xor         dword ptr [ebp-8],eax  
00BB2914  xor         eax,ebp  
00BB2916  push        eax  
00BB2917  lea         eax,[ebp-10h]  
00BB291A  mov         dword ptr fs:[00000000h],eax  
00BB2920  mov         dword ptr [i],0  
00BB2927  mov         dword ptr [success],0  
00BB292E  mov         dword ptr [ebp-4],0  
00BB2935  jmp         `eh vector constructor iterator'+50h (0BB2940h)  
00BB2937  mov         eax,dword ptr [i]  
00BB293A  add         eax,1  
00BB293D  mov         dword ptr [i],eax  
00BB2940  mov         ecx,dword ptr [i]  
00BB2943  cmp         ecx,dword ptr [count]   以上幾行形成一個迴圈控制結構
00BB2946  jge         `eh vector constructor iterator'+69h (0BB2959h)  
00BB2948  mov         ecx,dword ptr [ptr]  
00BB294B  call        dword ptr [pCtor]  呼叫Super()
00BB294E  mov         edx,dword ptr [ptr]  
00BB2951  add         edx,dword ptr [size]  
00BB2954  mov         dword ptr [ptr],edx  偏移到下一個物件
00BB2957  jmp         `eh vector constructor iterator'+47h (0BB2937h)  
00BB2959  mov         dword ptr [success],1  全部呼叫完建構函式
00BB2960  mov         dword ptr [ebp-4],0FFFFFFFEh  
00BB2967  call        `eh vector constructor iterator'+7Eh (0BB296Eh)  
00BB296C  jmp         $LN12 (0BB298Ah)  
$LN11:
00BB296E  cmp         dword ptr [success],0  
00BB2972  jne         `eh vector constructor iterator'+99h (0BB2989h)  
00BB2974  mov         eax,dword ptr [pDtor]  
00BB2977  push        eax  
00BB2978  mov         ecx,dword ptr [i]  
00BB297B  push        ecx  
00BB297C  mov         edx,dword ptr [size]  
00BB297F  push        edx  
00BB2980  mov         eax,dword ptr [ptr]  
00BB2983  push        eax  
00BB2984  call        __ArrayUnwind (0BB11EAh)  
$LN13:
00BB2989  ret  
$LN12:
00BB298A  mov         ecx,dword ptr [ebp-10h]  
00BB298D  mov         dword ptr fs:[0],ecx  
00BB2994  pop         ecx  
00BB2995  pop         edi  
00BB2996  pop         esi  
00BB2997  pop         ebx  
00BB2998  mov         esp,ebp  
00BB299A  pop         ebp  
00BB299B  ret         14h  
00BB2666  mov         edx,dword ptr [ebp-11Ch]  
00BB266C  add         edx,4  
00BB266F  mov         dword ptr [ebp-130h],edx  
00BB2675  jmp         main+0A1h (0BB2681h)  
00BB2677  mov         dword ptr [ebp-130h],0  
00BB2681  mov         eax,dword ptr [ebp-130h]  
00BB2687  mov         dword ptr [ebp-128h],eax  
00BB268D  mov         dword ptr [ebp-4],0FFFFFFFFh  
00BB2694  mov         ecx,dword ptr [ebp-128h]  
    22:     Super * sp = new Super[15];
00BB269A  mov         dword ptr [sp],ecx  最後將偏移後的指標返回給sp.

於是我們知道Super *sp = new Super[15]的過程,使用偽程式碼表示是 :

count =15;
size =sizeof(Super)
void * _p =malloc(Count*Size+4)
void * tp =(void *)(((char*)p)+4)
for I in 0..15:
    tp=(void*)(((char*)tp)+Size*I)
    ((Super*)tp)->Super();
*(int*)_p=Count
sp =(Super*)((int*)_p+1);

我們再來 Super *sp1 =new Super;的彙編程式碼

    23:     Super * sp1 = new Super;
012C269D  push        4  
012C269F  call        operator new (012C11BDh)  只申請4個位元組
012C26A4  add         esp,4  
012C26A7  mov         dword ptr [ebp-11Ch],eax  
012C26AD  mov         dword ptr [ebp-4],1  
012C26B4  cmp         dword ptr [ebp-11Ch],0  
012C26BB  je          main+0F0h (012C26D0h)  
012C26BD  mov         ecx,dword ptr [ebp-11Ch]  
012C26C3  call        Super::Super (012C1023h)  呼叫建構函式
012C26C8  mov         dword ptr [ebp-148h],eax  
012C26CE  jmp         main+0FAh (012C26DAh)  
012C26D0  mov         dword ptr [ebp-148h],0  
012C26DA  mov         eax,dword ptr [ebp-148h]  
012C26E0  mov         dword ptr [ebp-128h],eax  
012C26E6  mov         dword ptr [ebp-4],0FFFFFFFFh  
012C26ED  mov         ecx,dword ptr [ebp-128h]  
012C26F3  mov         dword ptr [sp1],ecx       返回operator的值

於是我們知道Super * sp1 =new Super的實際過程的虛擬碼為:

    void *_p =malloc(sizeof(Super))
    ((Super*)_p)->Super();
    sp1 =(Super*)_p;

通過檢視彙編程式碼,我們可以知道 delete[] sp;的過程是:

count =*(((int*)sp)-1)
for i in 0..count:
    (Sp+i)->~Super()
free(void*(((int*)sp)-1))

而delete sp1的過程就簡單的多了:

    (sp1)->~Super()
    free(sp1)

如果···

1.如果delete sp會發生什麼呢?

分析:

Sp指向的是陣列記憶體塊的第一個元素的起始地址,但它不是整個動態記憶體塊的起始處,換句話說,sp不是某次malloc的返回值,因而它無法被free處理,因為free只接受malloc一系列動態分配函式的返回值。目測,會有第一個~Super()會被呼叫,然後立馬程式會崩潰。因為它執行了類似於下面的語句:
    (sp)->~Super()
    free(sp);GG!!

測試程式碼:

類定義與上面一致,修改main():
int main()
{
    Super * sp = new Super[15];
    Super * sp1 = new Super;
    //printf("%d\n", *((char*)((int)sp1-4)));
    delete sp;
    delete sp1;
    //delete sp1;
    system("pause");
    return 0;
}

看執行結果:


14828836 Super()
14828840 Super()
14828844 Super()
14828848 Super()
14828852 Super()
14828856 Super()
14828860 Super()
14828864 Super()
14828868 Super()
14828872 Super()
14828876 Super()
14828880 Super()
14828884 Super()
14828888 Super()
14828892 Super()
14849392 Super()
14828836 ~Super
Open 052450C8 error!

的確如此,我們的分析是正確的。

2.如果delete[] sp1會怎麼樣?

分析:

delete [] sp1時會執行類似於下面的語句:
    count =*(((int*)sp)-1)      :Unknown value,未知的值
    for i in 0..count:  
        (Sp+i)->~Super()       如果Count不為0,那麼至少會呼叫一次~Super()
    free(void*(((int*)sp)-1))   GG!奔潰

測試程式碼:

類定義與上面一致,修改main():
int main()
{
    //Super * sp = new Super[15];
    Super * sp1 = new Super;
    //printf("%d\n", *((char*)((int)sp1-4)));
    //delete[] sp;
    delete[] sp1;
    //delete sp1;
    system("pause");
    return 0;
}

執行結果:

10437920 Super()
Open 04E850C8 error!

果然GG

總結:

1.Super *sp = new Super[15]的過程,使用偽程式碼表示是 :

    count =15;
    size =sizeof(Super)
    void * _p =malloc(Count*Size+4)
    void * tp =(void *)(((char*)p)+4)
    for I in 0..15:
        tp=(void*)(((char*)tp)+Size*I)
        ((Super*)tp)->Super();
    *(int*)_p=Count
    Sp =(Super*)((int*)_p+1);

2. Super * sp1 =new Super的實際過程的虛擬碼為:

    void *_p =malloc(sizeof(Super))
    ((Super*)_p)->Super();
    sp1 =(Super*)_p;

3.delete[] sp;的過程是:

    count =*(((int*)sp)-1)
    for I in 0..count:
        (Sp+i)->~Super()
    free(void*(((int*)sp)-1))

4.delete sp1的過程就簡單的多了

    (sp1)->~Super()
    free(sp1)

5.delete與new要使用相同的形式