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要使用相同的形式