Comportement étrange des destructeurs C ++

#include  #include  using namespace std; int main() { vector< vector > dp(50000, vector(4, -1)); cout << dp.size(); } 

Ce petit programme prend une fraction de seconde à exécuter lorsqu’il est simplement exécuté à partir de la ligne de commande. Mais lorsqu’il est exécuté dans un débogueur, cela prend plus de 8 secondes. Mettre en pause le débogueur révèle qu’il est en train de détruire tous ces vecteurs. WTF?

Remarque – Visual Studio 2008 SP1, CPU Core 2 Duo 6700 avec 2 Go de RAM.

Ajouté: Pour clarifier, non, je ne confonds pas les versions Debug et Release. Ces résultats sont sur un seul et même fichier .exe, sans aucune recompilation intermédiaire. En fait, basculer entre les versions Debug et Release ne change rien.

L’exécution dans le débogueur change la bibliothèque d’allocation de mémoire utilisée en une qui effectue beaucoup plus de vérification. Un programme qui ne fait que l’allocation de mémoire et la désaffectation va souffrir beaucoup plus qu’un programme “normal”.

Après avoir juste essayé d’exécuter votre programme sous VS, je reçois une stack d’appel qui ressemble à

 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::deallocate(int * _Ptr=0x0061f708, unsigned int __formal=4) desc.exe!std::vector >::_Tidy() Line 1134 C++ 

Ce qui montre les fonctions de débogage dans ntdll.dll et le runtime C utilisé.

L’exécution d’un programme avec le débogueur attaché est toujours plus lente que sans.

Cela doit être provoqué par le VS qui se connecte aux nouveaux appels / suppressions et en vérifiant davantage une fois connecté – ou la bibliothèque d’exécution utilise l’API IsDebuggerPresent et effectue des opérations différentes dans ce cas.

Vous pouvez facilement essayer ceci à partir de Visual Studio, démarrez le programme avec Debug-> Démarrer le débogage ou Debug-> Démarrer sans débogage. Sans débogage, c’est comme en ligne de commande, avec exactement la même configuration de construction et le même exécutable.

Le tas de débogage est automatiquement activé lorsque vous démarrez votre programme dans le débogueur, par opposition à l’attachement à un programme déjà en cours avec le débogueur.

Le livre Advanced Windows Debugging de Mario Hewardt et Daniel Pravat contient des informations correctes sur le tas de Windows, et il s’avère que le chapitre sur les tas est en exemple sur le site Web .

La page 281 contient une barre latérale sur “Association au démarrage du processus sous le débogueur”:

Lors du démarrage du processus sous le débogueur, le gestionnaire de segments modifie toutes les demandes afin de créer de nouveaux segments et modifie les indicateurs de création de segment afin d’activer les segments favorables au débogage (sauf si la variable d’environnement _NO_DEBUG_HEAP est définie sur 1). En comparaison, en se rattachant à un processus déjà en cours d’exécution, les segments du processus ont déjà été créés à l’aide des indicateurs de création de segment de mémoire par défaut et ne comportent pas d’indicateurs adaptés au débogage (à moins que ceux-ci ne soient explicitement définis par l’application).

(Également: une question semi-liée , où j’ai posté une partie de cette réponse auparavant.)

C’est certainement HeapFree qui ralentit le processus, vous pouvez obtenir le même effet avec le programme ci-dessous.

Passer des parameters comme HEAP_NO_SERIALIZE à HeapFree n’aide pas non plus.

 #include "stdafx.h" #include  #include  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); } 

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

lisez les sections 2 “PEB! NtGlobalFlags” et 2 “Drapeaux de tas”

pense que cela peut l’expliquer …


EDIT: solution ajoutée

dans votre gestionnaire pour CREATE_PROCESS_DEBUG_EVENT, ajoutez ce qui suit

 // 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); 

Oui, WTF en effet.

Vous savez que votre compilateur optimisera un grand nombre de ces appels de fonctions en les alignant, puis optimisera davantage le code afin d’exclure tout ce qui ne fait rien, ce qui dans le cas des vecteurs de int signifiera: à peu près pas beaucoup .

En mode débogage, l’inline n’est pas activée car cela rendrait le débogage très pénible.

C’est un bel exemple de la rapidité avec laquelle le code C ++ peut être.

8 secondes ?? J’ai essayé la même chose en mode débogage. Pas plus d’une demi-seconde je suppose. Êtes-vous sûr que ce sont les destructeurs?

FYI. Visual Studio 2008 SP1, processeur Core 2 Duo 6700 avec 2 Go de RAM.

cela n’a aucun sens pour moi – attacher un débogueur à un binary aléatoire dans une configuration normale devrait principalement intercepter les interruptions de points d’arrêt (asm int 3, etc.).