C ++析构函数的奇怪行为

时间:2009-02-10 12:21:10

标签: c++ visual-studio-2008 debugging destructor

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    vector< vector<int> > dp(50000, vector<int>(4, -1));
    cout << dp.size();
}

这个小程序在从命令行运行时需要一秒钟才能执行。但是在调试器中运行时,需要8秒钟。暂停调试器会发现它正在摧毁所有这些向量。 WTF?

注 - Visual Studio 2008 SP1,Core 2 Duo 6700 CPU,带2GB RAM。

已添加:澄清一下,不,我不会混淆Debug和Release版本。这些结果是在同一个.exe上,甚至没有任何重新编译。事实上,在Debug和Release版本之间切换没有任何改变。

8 个答案:

答案 0 :(得分:18)

在调试器中运行会将所使用的内存分配库更改为执行更多检查的内存分配库。除了内存分配和解除分配之外什么都不做的程序将比“正常”程序遭受更多的痛苦。

修改 刚刚尝试在VS下运行程序,我得到一个看起来像

的调用堆栈
ntdll.dll!_RtlpValidateHeapEntry@12()  + 0x117 bytes    
ntdll.dll!_RtlDebugFreeHeap@12()  + 0x97 bytes  
ntdll.dll!_RtlFreeHeapSlowly@12()  + 0x228bf bytes  
ntdll.dll!_RtlFreeHeap@12()  + 0x17646 bytes    
msvcr90d.dll!_free_base(void * pBlock=0x0061f6e8)  Line 109 + 0x13 bytes
msvcr90d.dll!_free_dbg_nolock(void * pUserData=0x0061f708, int nBlockUse=1)
msvcr90d.dll!_free_dbg(void * pUserData=0x0061f708, int nBlockUse=1) 
msvcr90d.dll!operator delete(void * pUserData=0x0061f708)
desc.exe!std::allocator<int>::deallocate(int * _Ptr=0x0061f708, unsigned int __formal=4)
desc.exe!std::vector<int,std::allocator<int> >::_Tidy()  Line 1134  C++

其中显示了ntdll.dll中的调试功能以及正在使用的C运行时。

答案 1 :(得分:3)

运行附加调试器的程序总是慢于没有。

这必须是由于VS挂钩到新的/删除调用并在附加时进行更多检查 - 或者运行时库使用IsDebuggerPresent API并在这种情况下做了不同的事情。

您可以从Visual Studio内部轻松尝试此操作,使用Debug-&gt; Start Debugging或Debug-&gt; Start Without Debugging启动程序。没有调试就像从命令行,具有完全相同的构建配置和可执行文件。

答案 2 :(得分:3)

在调试器中启动程序时,调试堆会自动启用,而不是使用调试器附加到已经运行的程序。

Mario Hewardt和Daniel Pravat的书 Advanced Windows Debugging 提供了关于Windows堆的一些不错的信息,事实证明,有关堆的章节是up on the web site as a sample chapter

第281页有一个关于“在调试器下附加启动进程”的侧栏:

  

开始下的过程   调试器,堆管理器修改   所有要求创建新堆和   将堆创建标志更改为   启用调试友好堆(除非   _NO_DEBUG_HEAP环境   变量设置为1)。相比下,   附加到已经运行的   过程中,堆过程中有   已使用默认值创建   堆创建标志,不会有   设置调试友好标志(除非   由应用程序明确设置。

(另外:a semi-related question,之前我在这里发布了部分答案。)

答案 3 :(得分:2)

绝对是HeapFree减慢速度,你可以通过下面的程序获得同样的效果。

将HEAP_NO_SERIALIZE等参数传递给HeapFree也无济于事。

#include "stdafx.h"
#include <iostream>
#include <windows.h>

using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
HANDLE heap = HeapCreate(0, 0, 0);

void** pointers = new void*[50000];

int i = 0;
for (i = 0; i < 50000; ++i)
{
    pointers[i] = HeapAlloc(heap, 0, 4 * sizeof(int));
}

cout << i;
for (i = 49999; i >= 0; --i)
{
    HeapFree(heap, 0, pointers[i]);
}

cout << "!";

delete [] pointers;

HeapDestroy(heap);
}

答案 4 :(得分:1)

http://www.symantec.com/connect/articles/windows-anti-debug-reference

阅读第2节“PEB!NtGlobalFlags”和2“堆标志”

认为这可以解释它......


编辑:添加解决方案

在CREATE_PROCESS_DEBUG_EVENT的处理程序中,添加以下内容

// hack 'Load Configuration Directory' in exe header to point to a new block that specfies GlobalFlags 
IMAGE_DOS_HEADER dos_header;
ReadProcessMemory(cpdi.hProcess,cpdi.lpBaseOfImage,&dos_header,sizeof(IMAGE_DOS_HEADER),NULL);
IMAGE_OPTIONAL_HEADER32 pe_header;
ReadProcessMemory(cpdi.hProcess,(BYTE*)cpdi.lpBaseOfImage+dos_header.e_lfanew+4+sizeof(IMAGE_FILE_HEADER),&pe_header,offsetof(IMAGE_OPTIONAL_HEADER32,DataDirectory),NULL);
IMAGE_LOAD_CONFIG_DIRECTORY32 ilcd;
ZeroMemory(&ilcd,sizeof(ilcd));
ilcd.Size = 64; // not sizeof(ilcd), as 2000/XP didn't have SEHandler
ilcd.GlobalFlagsClear = 0xffffffff; // clear all flags.  this is as we don't want dbg heap
BYTE *p = (BYTE *)VirtualAllocEx(cpdi.hProcess,NULL,ilcd.Size,MEM_COMMIT|MEM_RESERVE,PAGE_READWRITE);
WriteProcessMemory(cpdi.hProcess,p,&ilcd,ilcd.Size,NULL);
BYTE *dde = (BYTE*)cpdi.lpBaseOfImage+dos_header.e_lfanew+4+sizeof(IMAGE_FILE_HEADER)+offsetof(IMAGE_OPTIONAL_HEADER32,DataDirectory)+sizeof(IMAGE_DATA_DIRECTORY)*IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG;
IMAGE_DATA_DIRECTORY temp;
temp.VirtualAddress = p-cpdi.lpBaseOfImage;
temp.Size = ilcd.Size;
DWORD oldprotect;
VirtualProtectEx(cpdi.hProcess,dde,sizeof(temp),PAGE_READWRITE,&oldprotect);
WriteProcessMemory(cpdi.hProcess,dde,&temp,sizeof(temp),NULL);
VirtualProtectEx(cpdi.hProcess,dde,sizeof(temp),oldprotect,&oldprotect);

答案 5 :(得分:0)

是的,WTF确实。

你知道你的编译器会通过内联它们来优化很多这些函数调用,然后进一步优化那里的代码以排除任何实际上没有做任何事情的东西,在int的向量的情况下意味着:几乎不是很多。

在调试模式下,内联未打开,因为这会使调试变得糟糕。

这是C ++代码真正有多快的一个很好的例子。

答案 6 :(得分:0)

8秒?我在调试模式下尝试了相同的操作。我猜不到半秒钟。你确定它是析构函数吗?

FYI。 Visual Studio 2008 SP1,Core 2 Duo 6700 CPU,带2GB RAM。

答案 7 :(得分:-1)

对我来说毫无意义 - 在正常配置中将调试器附加到随机二进制文件应该主要捕获断点中断(asm int 3等)。