int *ptr = new int [10];
int *ptr_ = ptr;
delete [] ptr_;
上面这一段程序是可以实现动态创建一个数组最后将其释放的,但 ptr_
指针又根本没有和 10 这个长度有过联系,它又能释放一片空间,这里就有东西可以研究一下了。
new 和 delete 单个空间 链接到标题
new
会申请一个空间,如果有构造函数的话会调用构造函数,最后返回该地址。
delete
会传入一个地址,如果有析构函数的话就先调用析构函数,最后将这个空间释放。
new 和 delete 一片空间 链接到标题
入侵式 链接到标题
new [n]
会申请 size_t 个字节 + n * 对象大小,在前 size_t 个字节里存入数组的长度 n,如果有构造函数的话会调用 n 次构造函数,最后返回首地址向后偏移 size_t 个字节的地址。
delete []
会传入一个地址,然后编译器会从该地址前 size_t 个字节获取数组的长度n,如果有析构函数的话就先调用n次析构函数,最后将 size_t 个字节 + n * 对象大小的空间全部释放。
size_t 的具体大小和编译的环境有关,x86环境下它的大小是4,x64环境下它的大小是8.
汇编码: 链接到标题
__declspec(noinline) void foo(Object* p)
{
push ebp
mov ebp,esp
push 0FFFFFFFFh
push 0AB2AA0h
mov eax,dword ptr fs:[00000000h]
push eax
push esi
mov eax,dword ptr [__security_cookie (0AB5004h)]
xor eax,ebp
push eax
lea eax,[ebp-0Ch]
mov dword ptr fs:[00000000h],eax
delete[] p;
test ecx,ecx
je foo+4Fh (0AB1AAFh)
push offset Object::~Object (0AB1000h)
lea esi,[ecx-4]
mov dword ptr [ebp-4],0
push dword ptr [esi]
push 1
push ecx
call `eh vector destructor iterator' (0AB1B10h)
mov eax,dword ptr [esi]
add eax,4
push eax
push esi
call operator delete[] (0AB1AD2h)
add esp,8
}
mov ecx,dword ptr [ebp-0Ch]
mov dword ptr fs:[0],ecx
pop ecx
pop esi
mov esp,ebp
pop ebp
ret
这里 ecx-4
就验证了我上面的说法,因为是用 x86 环境编译的,所以这里就是用 4 个字节来存长度 n。
非入侵式 链接到标题
上面说的入侵式对程序员有一定要求,因为一旦越界就有可能破坏储存数组长度的空间,因此内存分配器有另外一种方法来储存长度n。
这种情况下底层会生成一张申请记录表,里面储存了每次申请内存的地址和大小,释放的时候从此表查询即可。