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。

这种情况下底层会生成一张申请记录表,里面储存了每次申请内存的地址大小,释放的时候从此表查询即可。