反汇编理解C++的new和delete
前言
如果你对C++的new和delete操作符还有些疑问的话,那么本文可能是你所需要的。
operator new和operator delete
在讲new和delete前我们先讲operator new和operator delete,operator new和operator delete是C++标准库中的函数,函数原型如下:
void* operator new(size_t); // 分配内存
void operator delete(void*); // 释放内存
这两个函数和我们普通函数不一样的是函数名中间有空格,我们自己写代码是不能这样做的,至于为什么要设计成这样我也不知道。我们把它们当作普通的函数就可以了,它两和C语言的malloc和free比较类似,我们在写C++代码的时候是可以直接使用这两个函数的,如下:
int main()
{
// 分配1024个字节的内存
void* pBuffer = operator new(1024);
// 释放内存
operator delete(pBuffer);
pBuffer = nullptr;
return 0;
}
第一个示例:new一个内置类型对象
new和delete是C++中的操作符,当我们使用new和delete时编译器帮助我们生成了一些代码,这些隐藏起来的代码可能就是困扰我们的地方。我们可以通过反汇编来探究其中的秘密,先看一个简单的示例:
int* pValue = new int;
delete pValue;
pValue = nullptr;
以上代码在VS中Debug模式下的反汇编为:
int* pValue = new int;
00C513AE push 4 ;参数入栈, 需要分配的字节大小
00C513B0 call operator new (0C51177h) ;调用函数, 分配内存
00C513B5 add esp,4 ;栈恢复
00C513B8 mov dword ptr [ebp-0E0h],eax ;临时变量中存储分配的内存地址
00C513BE mov eax,dword ptr [ebp-0E0h]
00C513C4 mov dword ptr [pValue],eax ;变量pValue存储分配的内存地址
delete pValue;
00C513C7 mov eax,dword ptr [pValue]
00C513CA mov dword ptr [ebp-0D4h],eax ;临时变量中存储分配的内存地址
00C513D0 mov ecx,dword ptr [ebp-0D4h]
00C513D6 push ecx ;参数入栈, 分配的内存地址
00C513D7 call operator delete (0C51082h) ;调用函数, 释放内存
00C513DC add esp,4 ;栈恢复
pValue = nullptr;
00C513DF mov dword ptr [pValue],0
我们看到使用new时会调用函数operator new,使用delete时会调用函数operator delete,并且new操作符还帮我们计算了需要分配的内存大小。
第二个示例:new一个类类型对象
string* pStr = new string();
delete pStr;
pStr = nullptr;
部分反汇编代码为:
string* pStr = new string();
001B15AD push 20h
001B15AF call operator new (1B1235h) ;分配内存
001B15B4 add esp,4
001B15B7 mov dword ptr [ebp-0F8h],eax
001B15BD mov dword ptr [ebp-4],0
001B15C4 cmp dword ptr [ebp-0F8h],0
001B15CB je main+70h (1B15E0h)
001B15CD mov ecx,dword ptr [ebp-0F8h]
001B15D3 call string::string(1B1037h) ;调用构造函数
001B15D8 mov dword ptr [ebp-10Ch],eax
001B15DE jmp main+7Ah (1B15EAh)
001B15E0 mov dword ptr [ebp-10Ch],0
001B15EA mov eax,dword ptr [ebp-10Ch]
001B15F0 mov dword ptr [ebp-104h],eax
001B15F6 mov dword ptr [ebp-4],0FFFFFFFFh
001B15FD mov ecx,dword ptr [ebp-104h]
001B1603 mov dword ptr [ebp-14h],ecx
delete pStr;
001B1606 mov eax,dword ptr [ebp-14h]
001B1609 mov dword ptr [ebp-0E0h],eax
001B160F mov ecx,dword ptr [ebp-0E0h]
001B1615 mov dword ptr [ebp-0ECh],ecx
001B161B cmp dword ptr [ebp-0ECh],0
001B1622 je main+0C9h (1B1639h)
001B1624 push 1
001B1626 mov ecx,dword ptr [ebp-0ECh]
001B162C call string::`scalar deleting destructor' (1B12A8h)
001B1631 mov dword ptr [ebp-10Ch],eax
001B1637 jmp main+0D3h (1B1643h)
001B1639 mov dword ptr [ebp-10Ch],0
pStr = nullptr;
001B1643 mov dword ptr [ebp-14h],0
string::`scalar deleting destructor':
001B16B0 push ebp
001B16B1 mov ebp,esp
001B16B3 sub esp,0CCh
001B16B9 push ebx
001B16BA push esi
001B16BB push edi
001B16BC push ecx
001B16BD lea edi,[ebp-0CCh]
001B16C3 mov ecx,33h
001B16C8 mov eax,0CCCCCCCCh
001B16CD rep stos dword ptr es:[edi]
001B16CF pop ecx
001B16D0 mov dword ptr [ebp-8],ecx
001B16D3 mov ecx,dword ptr [this]
001B16D6 call string::~string(1B11C7h) ;调用析构函数
001B16DB mov eax,dword ptr [ebp+8]
001B16DE and eax,1
001B16E1 je `scalar deleting destructor'+3Fh (1B16EFh)
001B16E3 mov eax,dword ptr [this]
001B16E6 push eax
001B16E7 call operator delete (1B10B9h) ;释放内存
001B16EC add esp,4
001B16EF mov eax,dword ptr [this]
001B16F2 pop edi
001B16F3 pop esi
001B16F4 pop ebx
001B16F5 add esp,0CCh
001B16FB cmp ebp,esp
001B16FD call @ILT+475(__RTC_CheckEsp) (1B11E0h)
001B1702 mov esp,ebp
001B1704 pop ebp
001B1705 ret 4
当使用new操作符创建一个类类型对象时实际上发生了3个步骤:
- 调用函数operator new分配内存
- 调用构造函数进行构造对象
- 返回构造的对象的指针
当使用delete操作符释放一个类类型对象时实际上发生了2个步骤:
- 调用析构函数进行析构对象
- 调用函数operator delete释放内存
operator new[]和operator delete[]
在C++标准库中也有如下两个函数:
void* operator new[](size_t); // 分配内存
void operator delete[](void*); // 释放内存
当我们分配数组时我们会使用new []和delete []操作符,new []和delete []操作符分别会调用operator new[]和operator delete[]这两个函数。
第三个示例:new一个内置类型对象数组
int* pArray = new int[3];
delete [] pArray;
pArray = nullptr;
反汇编代码为:
int* pArray = new int[3];
012813BE push 0Ch
012813C0 call operator new[] (12810A0h) ;调用函数分配内存
012813C5 add esp,4
012813C8 mov dword ptr [ebp-0E0h],eax
012813CE mov eax,dword ptr [ebp-0E0h]
012813D4 mov dword ptr [pArray],eax
delete [] pArray;
012813D7 mov eax,dword ptr [pArray]
012813DA mov dword ptr [ebp-0D4h],eax
012813E0 mov ecx,dword ptr [ebp-0D4h]
012813E6 push ecx
012813E7 call operator delete[] (128101Eh) ;调用函数释放内存
012813EC add esp,4
pArray = nullptr;
012813EF mov dword ptr [pArray],0
我们可以看到函数operator new[]和operator delete[]都有被调用。
第4个示例:new一个类类型对象数组
class Test
{
public:
Test()
{
}
~Test()
{
}
private:
int m_value1;
int m_value2;
};
int main()
{
Test* pArray = new Test[3];
delete[] pArray;
pArray = nullptr;
return 0;
}
反汇编代码为:
Test* pArray = new Test[3];
0104142D push 1Ch ;注意:这里分配的内存大小为28个字节
0104142F call operator new[] (10410AFh) ;分配内存
01041434 add esp,4
01041437 mov dword ptr [ebp-0F8h],eax
0104143D mov dword ptr [ebp-4],0
01041444 cmp dword ptr [ebp-0F8h],0
0104144B je main+97h (1041487h)
0104144D mov eax,dword ptr [ebp-0F8h]
01041453 mov dword ptr [eax],3
01041459 push offset Test::~Test (104105Fh)
0104145E push offset Test::Test (1041177h)
01041463 push 3
01041465 push 8
01041467 mov ecx,dword ptr [ebp-0F8h]
0104146D add ecx,4
01041470 push ecx
01041471 call `eh vector constructor iterator' (1041122h) ;迭代构造
01041476 mov edx,dword ptr [ebp-0F8h]
0104147C add edx,4
0104147F mov dword ptr [ebp-10Ch],edx
01041485 jmp main+0A1h (1041491h)
01041487 mov dword ptr [ebp-10Ch],0
01041491 mov eax,dword ptr [ebp-10Ch]
01041497 mov dword ptr [ebp-104h],eax
0104149D mov dword ptr [ebp-4],0FFFFFFFFh
010414A4 mov ecx,dword ptr [ebp-104h]
010414AA mov dword ptr [ebp-14h],ecx
delete[] pArray;
010414AD mov eax,dword ptr [ebp-14h]
010414B0 mov dword ptr [ebp-0E0h],eax
010414B6 mov ecx,dword ptr [ebp-0E0h]
010414BC mov dword ptr [ebp-0ECh],ecx
010414C2 cmp dword ptr [ebp-0ECh],0
010414C9 je main+0F0h (10414E0h)
010414CB push 3
010414CD mov ecx,dword ptr [ebp-0ECh]
010414D3 call Test::`vector deleting destructor' (1041041h); 析构
010414D8 mov dword ptr [ebp-10Ch],eax
010414DE jmp main+0FAh (10414EAh)
010414E0 mov dword ptr [ebp-10Ch],0
pArray = nullptr;
010414EA mov dword ptr [ebp-14h],0
从反汇编代码中我们可以看出我们需要分配的总内存大小为28字节,我们的每个Test对象占用8个字节,3个Test对象占用24个字节,为什么要多分配4个字节?其实这多分配出来的4个字节就是用来记录数组中对象的个数的。汇编代码中 eh vector constructor iterator 函数中有对应的构造函数的调用,vector deleting destructor 函数中有对应析构函数和operator delete[]函数的调用,具体汇编内容比较多,我们省略。
当使用new []操作符创建一个类类型对象数组时实际上发生了3个步骤:
- 调用函数operator new[]分配内存(比数组总大小多4个字节)
- 调用构造函数进行构造对象(构造次数为数组中对象的个数)
- 返回构造的数组的指针
当使用delete []操作符释放一个类类型对象数组时实际上发生了2个步骤:
- 对数组中每个对象分别进行析构
- 调用函数operator delete[]释放内存(包括数组的空间和记录数组大小的4个字节的空间,delete []操作符会正确找到需要释放的内存的地址)
重载new和delete
实际上new、delete、new []、delete[]这4个操作符是没办法重载的,但是我们可以重载它们所对应使用的内存分配函数:
void* operator new(size_t)
{
// 我的实现
}
void operator delete(void*)
{
// 我的实现
}
void* operator new[](size_t)
{
// 我的实现
}
void operator delete[](void*)
{
// 我的实现
}
类特定的new和delete
如果我们重载上面的4个函数,那么全局的new和delete都会被改变,有时候我们可能不希望这么做而仅仅针对某个类的new和delete。这样的话我们就需要定义类成员的operator new和operator delete函数:
class Test
{
public:
void* operator new(size_t size)
{
return malloc(size);
}
void operator delete(void* p)
{
free(p);
}
public:
Test()
{
}
~Test()
{
}
private:
int m_value1;
int m_value2;
};
这样的话我们每次new和delete Test对象时都会调用我们自己的operator new和operator delete函数,而全局的new和delete并不受影响。实际上类中的operator new和operator delete函数隐式地为静态函数,不必显式地将它们声明为static。 类中的operator new[]和operator delete[]函数同样如此。
每当我们new一个对象时,编译器都会去查找我们类中是否定义了operator new和operator delete函数,如果定义了就会使用我们自定义的函数,如果没定义就查找有没有全局的operator new和operator delete函数,如果也没有就会使用标准库中的operator new和operator delete函数。
还没有人评论...