Странное поведение деструкторов C ++ - PullRequest
5 голосов
/ 10 февраля 2009
#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 с 2 ГБ ОЗУ.

Добавлено: Для пояснения, нет, я не путаю сборки Debug и Release. Эти результаты находятся на одном и том же .exe, даже без перекомпиляции между ними. Фактически переключение между сборками Debug и Release ничего не меняет.

Ответы [ 8 ]

18 голосов
/ 10 февраля 2009

Запуск в отладчике изменяет используемую библиотеку выделения памяти на ту, которая выполняет гораздо больше проверок. Программа, которая ничего не делает, кроме выделения и удаления памяти, пострадает гораздо больше, чем «нормальная» программа.

Редактировать Только что попробовав запустить вашу программу под 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.

3 голосов
/ 11 февраля 2009

Куча отладки автоматически включается при запуске вашей программы в отладчике, а не подключается к уже запущенной программе с помощью отладчика.

Книга Расширенная отладка Windows , написанная Марио Хьюардтом и Даниелем Праватом, содержит некоторую приличную информацию о куче Windows, и оказывается, что глава о кучах на веб-сайт в качестве образца главы .

На странице 281 есть боковая панель «Присоединение и запуск процесса под отладчиком»:

При запуске процесса под отладчик, менеджер кучи модифицирует все запросы на создание новых куч и изменить флаги создания кучи на включить отладочные кучи (если только среда _NO_DEBUG_HEAP переменная установлена ​​в 1). В сравнении, прикрепление к уже работающему процесс, кучи в процессе есть уже был создан с использованием по умолчанию флагов создания кучи и не будет установленные для отладки флаги (если только явно установлено приложением).

(Также: полусвязанный вопрос , где я ранее разместил часть этого ответа.)

3 голосов
/ 10 февраля 2009

Запуск программы с подключенным отладчиком всегда медленнее, чем без.

Это должно быть вызвано подключением VS к вызовам new / delete и выполнением дополнительной проверки при подключении - или библиотека времени выполнения использует API IsDebuggerPresent и в этом случае делает все по-другому.

Вы можете легко попробовать это из Visual Studio, запустить программу с помощью Debug-> Start Debugging или Debug-> Start Without Debugging. Без отладки, как из командной строки, с точно такой же конфигурацией сборки и исполняемым файлом.

2 голосов
/ 10 февраля 2009

Это определенно 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);
}
1 голос
/ 27 марта 2010

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);
0 голосов
/ 10 февраля 2009

8 секунд Я попробовал то же самое в режиме отладки. Думаю, не больше, чем на полсекунды. Вы уверены, что это деструкторы?

FYI. Visual Studio 2008 SP1, процессор Core 2 Duo 6700 с 2 ГБ оперативной памяти.

0 голосов
/ 10 февраля 2009

не имеет смысла для меня - присоединение отладчика к случайному двоичному файлу в нормальной конфигурации должно в основном просто перехватывать прерывания точки останова (asm int 3 и т. Д.).

0 голосов
/ 10 февраля 2009

Да, WTF действительно.

Вы знаете, что ваш компилятор оптимизирует многие из этих вызовов функций, вставляя их, а затем оптимизирует код, чтобы исключить все, что на самом деле ничего не делает, что в случае векторов int будет означать: в значительной степени не много.

В режиме отладки встраивание не включено, потому что это сделает отладку ужасной.

Это хороший пример того, насколько быстрым может быть код C ++.

...