request-free-img

C++中 new/delete-new[]/delete[]底层原理

粉丝的观察

粉丝说,申请了基本类型的数组,但是用delete,在VS下居然没问题。

嗯,这个粉丝观察很仔细,但为什么会这样?这个视频我们一起来看看为什么。

示例代码

#include <iostream>
int main()
{
int* p1 = new int[3]{ 1,2,3 };
delete p1;
int* p2 = new int[3]{ 4,5,6 };
delete[]p2;
}

仍然是几行非常简单的C++代码,p1, p2都是一个包含3个Int类型的数组。

但是p1采用单对象 delete来释放,但是p2采用数组 delete来释放,咱们看看它们到底有什么区别。

单对象delete的汇编实现

还是看汇编代码,前边这些是p1内存分配,咱们就跳过它,直接看delete p1;的汇编实现。

008121C5 mov eax,dword ptr [p1]

这一行读出p1的指针。

008121C8 mov dword ptr [ebp-0ECh],eax

然后把它放到临时槽中。

008121CE push 4

这一行压入参数 4,这是C++14的sized delete 形参,4指的是单对象大小,也就是sizeof(int)的大小。

请注意这里3个int理论应该是12,但是它传的大小只是4。

008121D0 mov ecx,dword ptr [ebp-0ECh]
008121D6 push ecx

然后这2行压入待释放指针参数,也是做为delete的第一个参数传入。

008F21D7 call operator delete (08F10A0h)

紧接着调用 单对象版 operator delete,从汇编代码可以看出调用这个 单对象版的delete传入了2个参数,第一个参数是待释放的内存指针,第2个参数是4。

operator delete函数分析

void __CRTDECL operator delete(void* const block, size_t const)

可以看到operator delete的确是有2个参数。

好咱们看看delete函数的实现:

008F2FF0 push ebp
008F2FF1 mov ebp,esp

前边这里是常规函数序言,用于建立栈帧。

008F2FF3 mov eax,dword ptr [block]
008F2FF6 push eax
008F2FF7 call operator delete (08F10D7h)

接下来它会调用 operator delete,但是它只传了一个参数block,也就是待释放内存地址。

看到了吗,它忽略了第二个参数 size_t。

单参数operator delete实现

void __CRTDECL operator delete(void* const block) noexcept
{
00813B30 push ebp
00813B31 mov ebp,esp
    #ifdef _DEBUG
    _free_dbg(block, _UNKNOWN_BLOCK);
00813B33 push 0FFFFFFFFh
00813B35 mov eax,dword ptr [block]
00813B38 push eax
00813B39 call __free_dbg (0811393h)
00813B3E add esp,8
    #else
    free(block);
    #endif
}

可以看到,这个delete函数只接受一个参数,在debug模式下会调用_free_dbg而在release模式下会调用free来对内存进行释放。

数组delete[]的汇编实现

好,咱们再来看看P2指针相关的内存操作实现,这些内存申请操作也直接跳过,直接着看看数组delete的汇编代码实现。

00812264 mov eax,dword ptr [p2]

这一行读取p2的指针。

00812267 mov dword ptr [ebp-104h],eax

然后把它保存到一个临时槽。

0081226D mov ecx,dword ptr [ebp-104h]
00812273 push ecx

然后压入待释放指针。

008F2274 call operator delete[] (08F1320h)

这里是调用 数组版 operator delete[]。

到这里我们可以知道在调用单对象版 operator delete传入了2个参数,而调用数组版 operator delete时则只传入一个参数。

数组版operator delete[]分析

void __CRTDECL operator delete[](void* const block)

可以看到 数组版operator delete的汇编代码实现和单对象版的operator delete数据实现差不多是一样的,它只接受一个block参数,之后会把这个参数传给 operator delete。

结论与注意事项

现在,我们就能知道,对 POD 类型,在MS VS中的实现其实是一样的,这也是视频开头粉丝说的不会出错的原因。

即然如此,是不是说,对于传统数据类型,用new数组申请的内存,就可以使用delete来释放了呢?

仍然不建议这么做的,因为对于常规数据类型:在 MSVS的默认实现中,看起来相同。但是在不同编译器或者配置下(特别是启用数组 cookie、调试信息、对齐优化时)则有可能会改变布局,混用时可能会导致内存越界或 heap corruption。


更多问题探讨,请关注公众号:程序员角