Поделиться через


Vector Deleting Destructor

Today one guy in my team asked a question regarding the behavior of delete[] operator in C++ - how does the program know it needs to call CBar::~CBar instead of CFoo::~CFoo?

Note that the vector deleting destructor is a feature in Microsoft C++ Compiler, not required by the C++ standard.

 #define _CRTDBG_MAP_ALLOC
#include <malloc.h>
#include <crtdbg.h>

class CFoo
{
public:
  virtual ~CFoo() = 0;
};

class CBar
{
  virtual ~CBar()
  {
    _CrtDbgBreak();
  }
};

void __cdecl main()
{
  CBar* p = new CBar[1];
  delete[] (CFoo*)p; // what does this mean?
  _CrtDumpMemoryLeaks();
} 

Well the short answer would be - how does virtual destructor work and how does the program know the actual number of elements in the array?

If you've spent time on understanding the object layout in C++ and COM, you would know that virtual functions are backed by vtable, and it will not be too difficult to understand BSTR.

So we are ready for the answer - delete[] is a combination of BSTR and virtual function.

 CBar::`vector deleting destructor':
000007F7CD2E1140  mov         dword ptr [rsp+10h],edx  
000007F7CD2E1144  mov         qword ptr [rsp+8],rcx  
000007F7CD2E1149  sub         rsp,28h  
000007F7CD2E114D  mov         eax,dword ptr [rsp+38h]  
000007F7CD2E1151 and eax,2
000007F7CD2E1154 test eax,eax
000007F7CD2E1156 je CBar::`vector deleting destructor'+5Eh (07F7CD2E119Eh)  
000007F7CD2E1158  lea         r9,[CBar::~CBar (07F7CD2E1120h)] ; the address of CBar::~CBar 
000007F7CD2E115F  mov         rax,qword ptr [this]             ; this pointer  
000007F7CD2E1164  mov         r8d,dword ptr [rax-8]            ; array size *((size_t*)this - 1)  
000007F7CD2E1168  mov         edx,8                            ; sizeof(CBar)  
000007F7CD2E116D  mov         rcx,qword ptr [this]             ; this pointer  
000007F7CD2E1172  call        `vector destructor iterator' (07F7CD2E1220h)  

As you can see here, the vector deleting destructor was emitted as a virtual function in vtable, it takes a flag parameter, if flag & 0x2 equals to true, the actual function vector destructor iterator would get called.

The size of array was stored using the BSTR approach, passed in via the r8d register.

The callstack from Visual Studio Debugger also tells us the same thing:

 crtdbg.exe!`vector destructor iterator'(void * __t, unsigned __int64 __s, int __n, void (void *) * __f)  C++
crtdbg.exe!CBar::`vector deleting destructor'(unsigned int) C++
crtdbg.exe!main() Line 22    C++
crtdbg.exe!__tmainCRTStartup() Line 536  C
crtdbg.exe!mainCRTStartup() Line 377   C
kernel32.dll!BaseThreadInitThunk()    Unknown
ntdll.dll!RtlUserThreadStart()   Unknown

Regarding the actual meaning of the flag I mentioned, I'll leave it as a homework for the readers (hint: you may try out the DGML tool).