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