关于new和delete 一些不得不说的事

开发 后端
new和delete在C++中作用很大,但是当你写下new和delete的时候,到底发生了什么事,你了解过吗?本文为你讲述当你写下new和delete的时候,到底发生了什么事。

当你写下new和delete的时候,到底发生了什么事呢,让我们来做个试验看看。

写一段小代码:

  1. class a  
  2. {  
  3. public:  
  4.  a()  
  5.  {  
  6.   foo();  
  7.  }  
  8.  int foo()  
  9.  {  
  10.   return 0;  
  11.  }  
  12.  
  13.  ~a()  
  14.  {  
  15.   bar();  
  16.  }  
  17.  
  18.  int bar()  
  19.  {  
  20.   return 1;  
  21.  }  
  22. };  
  23.  
  24. int _tmain(int argc, _TCHAR* argv[])  
  25. {  
  26.  a* tmp = new a();  
  27.  delete tmp;  
  28.  return 0;  
  29. }  

在main函数的第一句下断点,调试,然后开汇编窗口输出结果:

  1. int _tmain(int argc, _TCHAR* argv[])  
  2. {  
  3. 004113F0  push        ebp    
  4. 004113F1  mov         ebp,esp   
  5. 004113F3  push        0FFFFFFFFh   
  6. 004113F5  push        offset __ehhandler$_wmain (41478Eh)   
  7. 004113FA  mov         eax,dword ptr fs:[00000000h]   
  8. 00411400  push        eax    
  9. 00411401  sub         esp,100h   
  10. 00411407  push        ebx    
  11. 00411408  push        esi    
  12. 00411409  push        edi    
  13. 0041140A  lea         edi,[ebp-10Ch]   
  14. 00411410  mov         ecx,40h   
  15. 00411415  mov         eax,0CCCCCCCCh   
  16. 0041141A  rep stos    dword ptr es:[edi]   
  17. 0041141C  mov         eax,dword ptr [___security_cookie (418000h)]   
  18. 00411421  xor         eax,ebp   
  19. 00411423  push        eax    
  20. 00411424  lea         eax,[ebp-0Ch]   
  21. 00411427  mov         dword ptr fs:[00000000h],eax   
  22.  /*a* tmp = new a();*/ 
  23. 0041142D  push        1      
  24. 0041142F  call        operator new (4111A4h)   
  25. 00411434  add         esp,4   
  26. 00411437  mov         dword ptr [ebp-0F8h],eax   
  27. 0041143D  mov         dword ptr [ebp-4],0   
  28. 00411444  cmp         dword ptr [ebp-0F8h],0   
  29. 0041144B  je          wmain+70h (411460h)   
  30. 0041144D  mov         ecx,dword ptr [ebp-0F8h]   
  31. 00411453  call        a::a (41101Eh)   
  32. 00411458  mov         dword ptr [ebp-10Ch],eax   
  33. 0041145E  jmp         wmain+7Ah (41146Ah)   
  34. 00411460  mov         dword ptr [ebp-10Ch],0   
  35. 0041146A  mov         eax,dword ptr [ebp-10Ch]   
  36. 00411470  mov         dword ptr [ebp-104h],eax   
  37. 00411476  mov         dword ptr [ebp-4],0FFFFFFFFh   
  38. 0041147D  mov         ecx,dword ptr [ebp-104h]   
  39. 00411483  mov         dword ptr [ebp-14h],ecx   
  40.  /*delete tmp;*/ 
  41. 00411486  mov         eax,dword ptr [ebp-14h]   
  42. 00411489  mov         dword ptr [ebp-0E0h],eax   
  43. 0041148F  mov         ecx,dword ptr [ebp-0E0h]   
  44. 00411495  mov         dword ptr [ebp-0ECh],ecx   
  45. 0041149B  cmp         dword ptr [ebp-0ECh],0   
  46. 004114A2  je          wmain+0C9h (4114B9h)   
  47. 004114A4  push        1      
  48. 004114A6  mov         ecx,dword ptr [ebp-0ECh]   
  49. 004114AC  call        a::`scalar deleting destructor' (41117Ch)   
  50. 004114B1  mov         dword ptr [ebp-10Ch],eax   
  51. 004114B7  jmp         wmain+0D3h (4114C3h)   
  52. 004114B9  mov         dword ptr [ebp-10Ch],0   
  53.  /*return 0;*/ 
  54. 004114C3  xor         eax,eax   
  55. }  
  56. 004114C5  mov         ecx,dword ptr [ebp-0Ch]   
  57. 004114C8  mov         dword ptr fs:[0],ecx   
  58. 004114CF  pop         ecx    
  59. 004114D0  pop         edi    
  60. 004114D1  pop         esi    
  61. 004114D2  pop         ebx    
  62. 004114D3  add         esp,10Ch   
  63. 004114D9  cmp         ebp,esp   
  64. 004114DB  call        @ILT+345(__RTC_CheckEsp) (41115Eh)   
  65. 004114E0  mov         esp,ebp   
  66. 004114E2  pop         ebp    
  67. 004114E3  ret     

前面一片调整stack,插入安全代码,设置异常处理等的操作不是今天我们要说的重点,直接跳到a* tmp = new a();这一句产生的反汇编:

  1. 0041142F call operator new (4111A4h) 

我们很明确的看到调用了一个函数operator new。继续跟进operator new看到底做了什么事情:

  1. void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)  
  2.         {       // try to allocate size bytes  
  3.         void *p;  
  4.         while ((p = malloc(size)) == 0)  
  5.                 if (_callnewh(size) == 0)  
  6.                 {       // report no memory  
  7.                 static const std::bad_alloc nomem;  
  8.                 _RAISE(nomem);  
  9.                 }  
  10.  
  11.         return (p);  
  12.         }  

很意外吧,其实operator new函数就做了那么一件事情:调用malloc函数分配内存。有没有负责调用构造函数?这个真没有。。。

#p#

那构造函数到底是谁调用的?看operator new下面的那片汇编代码:

  1. 00411434  add         esp,4   
  2. 00411437  mov         dword ptr [ebp-0F8h],eax   
  3. 0041143D  mov         dword ptr [ebp-4],0   
  4. 00411444  cmp         dword ptr [ebp-0F8h],0   
  5. 0041144B  je          wmain+70h (411460h)   
  6. 0041144D  mov         ecx,dword ptr [ebp-0F8h]   
  7. 00411453  call        a::a (41101Eh)  

出去将返回值赋给tmp的操作,我们看到了一处函数调用:

  1. 00411453 call a::a (41101Eh)  

没错,对类a的构造函数的调用,是编译器偷偷在你的函数里插入的,当时的情况就是如此。delete的情况也是一摸一样。
再来看针对对象数组的new和delete:

  1. class a  
  2. {  
  3. public:  
  4.  a()  
  5.  {  
  6.   int i1;  
  7.   int j1 = 0;  
  8.   static int k1;  
  9.   static int l1 = 0;  
  10.   foo();  
  11.  }  
  12.  int foo()  
  13.  {  
  14.   return 0;  
  15.  }  
  16.  
  17.  ~a()  
  18.  {  
  19.   int i2;  
  20.   int j2 = 0;  
  21.   static int k2;  
  22.   static int l2 = 0;  
  23.   bar();  
  24.  }  
  25.  
  26.  int bar()  
  27.  {  
  28.   return 1;  
  29.  }  
  30. };  
  31.  
  32. int _tmain(int argc, _TCHAR* argv[])  
  33. {  
  34.  a* tmp = new a[10];  
  35.  delete[] tmp;  
  36.  return 0;  
  37. }  

反汇编之后的结果如下:

  1. int _tmain(int argc, _TCHAR* argv[])  
  2. {  
  3. 004113F0  push        ebp    
  4. 004113F1  mov         ebp,esp   
  5. 004113F3  push        0FFFFFFFFh   
  6. 004113F5  push        offset __ehhandler$_wmain (41478Eh)   
  7. 004113FA  mov         eax,dword ptr fs:[00000000h]   
  8. 00411400  push        eax    
  9. 00411401  sub         esp,100h   
  10. 00411407  push        ebx    
  11. 00411408  push        esi    
  12. 00411409  push        edi    
  13. 0041140A  lea         edi,[ebp-10Ch]   
  14. 00411410  mov         ecx,40h   
  15. 00411415  mov         eax,0CCCCCCCCh   
  16. 0041141A  rep stos    dword ptr es:[edi]   
  17. 0041141C  mov         eax,dword ptr [___security_cookie (418000h)]   
  18. 00411421  xor         eax,ebp   
  19. 00411423  push        eax    
  20. 00411424  lea         eax,[ebp-0Ch]   
  21. 00411427  mov         dword ptr fs:[00000000h],eax   
  22.  a* tmp = new a[10];  
  23. 0041142D  push        0Eh    
  24. 0041142F  call        operator new (4111A4h)   
  25. 00411434  add         esp,4   
  26. 00411437  mov         dword ptr [ebp-0F8h],eax   
  27. 0041143D  mov         dword ptr [ebp-4],0   
  28. 00411444  cmp         dword ptr [ebp-0F8h],0   
  29. 0041144B  je          wmain+97h (411487h)   
  30. 0041144D  mov         eax,dword ptr [ebp-0F8h]   
  31. 00411453  mov         dword ptr [eax],0Ah   
  32. 00411459  push        offset a::`scalar deleting destructor' (41100Ah)   
  33. 0041145E  push        offset a::a (41101Eh)   
  34. 00411463  push        0Ah    
  35. 00411465  push        1      
  36. 00411467  mov         ecx,dword ptr [ebp-0F8h]   
  37. 0041146D  add         ecx,4   
  38. 00411470  push        ecx    
  39. 00411471  call        `eh vector constructor iterator' (4111F9h)   
  40. 00411476  mov         edx,dword ptr [ebp-0F8h]   
  41. 0041147C  add         edx,4   
  42. 0041147F  mov         dword ptr [ebp-10Ch],edx   
  43. 00411485  jmp         wmain+0A1h (411491h)   
  44. 00411487  mov         dword ptr [ebp-10Ch],0   
  45. 00411491  mov         eax,dword ptr [ebp-10Ch]   
  46. 00411497  mov         dword ptr [ebp-104h],eax   
  47. 0041149D  mov         dword ptr [ebp-4],0FFFFFFFFh   
  48. 004114A4  mov         ecx,dword ptr [ebp-104h]   
  49. 004114AA  mov         dword ptr [ebp-14h],ecx   
  50.  delete[] tmp;  
  51. 004114AD  mov         eax,dword ptr [ebp-14h]   
  52. 004114B0  mov         dword ptr [ebp-0E0h],eax   
  53. 004114B6  mov         ecx,dword ptr [ebp-0E0h]   
  54. 004114BC  mov         dword ptr [ebp-0ECh],ecx   
  55. 004114C2  cmp         dword ptr [ebp-0ECh],0   
  56. 004114C9  je          wmain+0F0h (4114E0h)   
  57. 004114CB  push        3      
  58. 004114CD  mov         ecx,dword ptr [ebp-0ECh]   
  59. 004114D3  call        a::`vector deleting destructor' (4111F4h)   
  60. 004114D8  mov         dword ptr [ebp-10Ch],eax   
  61. 004114DE  jmp         wmain+0FAh (4114EAh)   
  62. 004114E0  mov         dword ptr [ebp-10Ch],0   
  63.  return 0;  
  64. 004114EA  xor         eax,eax   
  65. }  
  66. 004114EC  mov         ecx,dword ptr [ebp-0Ch]   
  67. 004114EF  mov         dword ptr fs:[0],ecx   
  68. 004114F6  pop         ecx    
  69. 004114F7  pop         edi    
  70. 004114F8  pop         esi    
  71. 004114F9  pop         ebx    
  72. 004114FA  add         esp,10Ch   
  73. 00411500  cmp         ebp,esp   
  74. 00411502  call        @ILT+345(__RTC_CheckEsp) (41115Eh)   
  75. 00411507  mov         esp,ebp   
  76. 00411509  pop         ebp    
  77. 0041150A  ret           
  78.   

其他部分都大同小异,关键的不同在编译器插入的,用于初始化的代码:

  1. 00411459  push        offset a::`scalar deleting destructor' (41100Ah)   
  2. 0041145E  push        offset a::a (41101Eh)   
  3. 00411463  push        0Ah    
  4. 00411465  push        1      
  5. 00411467  mov         ecx,dword ptr [ebp-0F8h]   
  6. 0041146D  add         ecx,4   
  7. 00411470  push        ecx    
  8. 00411471  call        `eh vector constructor iterator' (4111F9h) 

我们看到数组大小0Ah,构造函数的地址41101Eh都被压入栈中,作为某函数的参数。到底是什么函数呢?就是:

  1. 00411471  call        `eh vector constructor iterator' (4111F9h) 

一个名为`eh vector constructor iterator' 的函数。我们还注意到a类的析构函数的地址也被当成参数传入,这是干什么用的呢?构造函数里为什么要析构函数的地址?比如在遍历调用构造函数的过程中,前8个都是没问题的,到第9个突然资源不足调用失败了,那么在返回前无论如何也要先把前8个的析构函数调用一遍,防止资源泄露。

delete[]的过程也大同小异,不过一个很有趣的地方是,“vector deleting destructor'”是a类的成员函数,而与‘eh vector constructor iterator’对应的`eh vector destructor iterator'函数在“vector deleting destructor'”函数内部:

  1. 004134AD  call        `eh vector destructor iterator' (411203h)  

。。。

  1. 004134C1  call        operator delete (4110A0h) 

回收内存的操作,也在a::`vector deleting destructor'里。

【编辑推荐】

  1. 《Java编程思想》作者:C++不垃圾,只是Java很傲慢
  2. Java与C++语言在作用域上的差异浅析
  3. C/C++使用多种方法获取文件大小代码
  4. C++类成员函数的重载、覆盖与隐藏
  5. 在C++中使用Lambda函数提高代码性能
责任编辑:yangsai 来源: gussing是个错别字
相关推荐

2010-05-26 15:17:06

Windows Emb

2009-05-20 13:03:40

Visual StudSilverlight微软

2014-04-15 10:18:24

中文女工科男

2019-10-18 17:55:03

安全运营

2019-12-24 14:04:59

PythonExcel数据处理

2020-06-15 08:19:00

ZooKeeperEureka

2019-11-14 15:38:46

AndroidRelease项目

2014-10-21 11:05:52

英特尔Linux

2011-04-26 09:44:05

Power Cloud

2018-08-06 11:59:00

混合云数据中心上云

2015-08-31 14:12:12

DockerKubernetesPaaS

2011-04-27 10:31:29

兼容墨盒用户体验

2010-05-19 15:45:52

统一通信VoIP

2012-08-30 10:15:50

IPv6

2015-02-05 09:32:19

单元测试

2015-01-16 16:44:50

2024-02-04 00:00:03

运维Linux磁盘

2015-12-11 11:58:51

光合资本

2011-05-31 09:29:31

C++Linus Torva

2022-10-27 09:55:00

点赞
收藏

51CTO技术栈公众号