Attraper les fuites de mémoire en C / C ++


Je vous salue, Khabrovchians!


Aujourd'hui, je veux ouvrir un peu de lumière sur la façon de gérer la fuite de mémoire en C ou C ++.


Il y a déjà deux articles sur Habré, à savoir: Nous traitons des fuites de mémoire (C ++ CRT) et des fuites de mémoire en C ++: Visual Leak Detector . Cependant, je pense qu'elles ne sont pas suffisamment divulguées, ou que ces méthodes peuvent ne pas donner le résultat dont vous avez besoin, je voudrais donc faire dans la mesure du possible toutes les méthodes disponibles afin de vous faciliter la vie.


Windows - développement
Commençons par Windows, à savoir le développement pour Visual Studio, car la plupart des programmeurs novices écrivent spécifiquement pour cet IDE.



Pour comprendre ce qui se passe, j'applique un vrai exemple:


Main.c
struct Student create_student(); void ControlMenu(); int main() { ControlMenu(); return 0; } void ShowListMenu(int kX) { char listMenu[COUNT_LIST_MENU][55] = { {"Read students from file"}, {"Input student and push"}, {"Input student and push it back"}, {"Input student and push it after student"}, {"Delete last student"}, {"Write students to file"}, {"Find student"}, {"Sort students"}, {"Show list of students"}, {"Exit"} }; for (int i = 0; i < COUNT_LIST_MENU; i++) { if (i == kX) { printf("%s", listMenu[i]); printf(" <=\n"); } else printf("%s\n", listMenu[i]); } } void ControlMenu() { struct ListOfStudents* list = NULL; int kX = 0, key; int exit = FALSE; ShowListMenu(kX); do { key = _getch(); switch (key) { case 72: //up { if (kX == 0) kX = COUNT_LIST_MENU-1; else kX--; }break; case 80: //down { if (kX == COUNT_LIST_MENU-1) kX = 0; else kX++; }break; case 13: { if (kX == 0) { int sizeStudents = 0; struct Student* students = (struct Student*)malloc(1 * sizeof(struct Student)); char* path = (char*)malloc(255 * sizeof(char)); printf("Put the path to file with students: "); scanf("%s", path); int size = 0; students = read_students(path, &size); if (students == NULL) { printf("Can't open this file.\n"); } else { for (int i = 0; i < size; i++) { if (i == 0) { list = init(students[i]); } else { list = add_new_elem_to_start(list, students[i]); } } } free(students); printf("\nPress any key to continue..."); getchar(); getchar(); free(path); } else if (kX == 1 || kX == 2 || kX == 3 || kX == 6) { struct Student student = create_student(); if (kX == 1) { if (list == NULL) { list = init(student); } else { list = add_new_elem_to_start(list, student); } printf("\nPress any key to continue..."); getchar(); getchar(); } else if (kX == 2) { if (list == NULL) { list = init(student); } else { list = add_new_elem_to_end(list, student); } printf("\nPress any key to continue..."); getchar(); getchar(); } else if (kX == 3) { if (list == NULL) { list = init(student); printf("The list was empty, so, list have been created.\n"); } else { int position; printf("Put the position: "); scanf("%d", &position); list = add_new_elem_after_pos(list, student, position); } printf("\nPress any key to continue..."); getchar(); getchar(); } else { if (find_elem(list, student)) printf("Student exist"); else printf("Student doesn't exist"); printf("\nPress any key to continue..."); getchar(); getchar(); } } else if (kX == 4) { if (list == NULL) { printf("List is empty.\n"); } else { list = delete_elem(list); } printf("\nPress any key to continue..."); getchar(); getchar(); } else if (kX == 5) { char* path = (char*)malloc(255 * sizeof(char)); printf("Put the path to file with students: "); scanf("%s", path); if (write_students(list, path) == 0) { printf("Can't write"); printf("\nPress any key to continue..."); getchar(); getchar(); } free(path); } else if (kX == 7) { if (list == NULL) { printf("List is empty.\n"); } else { list = sort_list(list); } printf("\nThe list was successfully sorted"); printf("\nPress any key to continue..."); getchar(); getchar(); } else if (kX == 8) { system("cls"); show_list(list); printf("\nPress any key to continue..."); getchar(); getchar(); } else exit = TRUE; }break; case 27: { exit = TRUE; }break; } system("cls"); ShowListMenu(kX); } while (exit == FALSE); while (list != NULL) { list = delete_elem(list); } } struct Student create_student() { struct Student new_student; do { printf("Write the name of student\n"); scanf("%s", new_student.first_name); } while (strlen(new_student.first_name) == 0); do { printf("Write the last name of student\n"); scanf("%s", new_student.last_name); } while (strlen(new_student.last_name) == 0); do { printf("Write the patronyminc of student\n"); scanf("%s", new_student.patronyminc); } while (strlen(new_student.patronyminc) == 0); do { printf("Write the city of student\n"); scanf("%s", new_student.city); } while (strlen(new_student.city) == 0); do { printf("Write the district of student\n"); scanf("%s", new_student.disctrict); } while (strlen(new_student.disctrict) == 0); do { printf("Write the country of student\n"); scanf("%s", new_student.country); } while (strlen(new_student.country) == 0); do { printf("Write the phone number of student\n"); scanf("%s", new_student.phoneNumber); } while (strlen(new_student.phoneNumber) != 13); char* choose = (char*)malloc(255 * sizeof(char)); while (TRUE) { printf("Does student live in hostel? Y - yes, N - no\n"); scanf("%s", choose); if (strcmp(choose, "y") == 0 || strcmp(choose, "Y") == 0) { new_student.is_live_in_hostel = TRUE; break; } if (strcmp(choose, "n") == 0 || strcmp(choose, "n") == 0) { new_student.is_live_in_hostel = FALSE; break; } } while (TRUE) { printf("Does student get scholarship? Y - yes, N - no\n"); scanf("%s", choose); if (strcmp(choose, "y") == 0 || strcmp(choose, "Y") == 0) { new_student.is_live_in_hostel = TRUE; break; } if (strcmp(choose, "n") == 0 || strcmp(choose, "n") == 0) { new_student.is_live_in_hostel = FALSE; break; } } free(choose); for (int i = 0; i < 3; i++) { char temp[10]; printf("Write the %d mark of ZNO\n", i + 1); scanf("%s", temp); new_student.mark_zno[i] = atof(temp); if (new_student.mark_zno[i] == 0) { i--; } } return new_student; } 

Et il y a aussi Student.h et Student.c dans lesquels les structures et les fonctions sont déclarées.


Il y a une tâche: démontrer l'absence de fuites de mémoire. La première chose qui me vient à l'esprit est le CRT. Ici, tout est assez simple.


En haut du fichier où se trouve main, ajoutez ce morceau de code:


 #define __CRTDBG_MAP_ALLOC #include <crtdbg.h> #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__) #define new DEBUG_NEW 

Et avant de return 0 vous devez enregistrer ceci: _CrtDumpMemoryLeaks(); .


Par conséquent, en mode Debug, le studio affichera ceci:


 Detected memory leaks! Dumping objects -> {79} normal block at 0x00A04410, 376 bytes long. Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD Object dump complete. 

Super! Vous savez maintenant que vous avez une fuite de mémoire. Maintenant, vous devez éliminer cela, il vous suffit donc de savoir où nous oublions d'effacer la mémoire. Et là se pose un problème: où, en fait, cette mémoire a-t-elle été allouée?



Après avoir répété toutes les étapes, j'ai découvert que la mémoire est perdue quelque part ici:


 if (kX == 0) { int sizeStudents = 0; struct Student* students = (struct Student*)malloc(1 * sizeof(struct Student)); char* path = (char*)malloc(255 * sizeof(char)); printf("Put the path to file with students: "); scanf("%s", path); int size = 0; students = read_students(path, &size); if (students == NULL) { printf("Can't open this file.\n"); } else { for (int i = 0; i < size; i++) { if (i == 0) { list = init(students[i]); } else { list = add_new_elem_to_start(list, students[i]); } } } free(students); printf("\nPress any key to continue..."); getchar(); getchar(); free(path); } 

Mais comment est-ce? Suis-je en train de tout libérer? Ou pas?


Et ici, j'ai vraiment raté Valgrind, avec sa trace d'appel ...


En conséquence, après 15 minutes de marche, j'ai trouvé un analogue de Valgrind - Visual Leak Detector . Il s'agit d'une bibliothèque tierce, un wrapper sur CRT qui a promis de montrer le traçage! Voilà ce dont j'ai besoin.


Pour l'installer, vous devez aller dans le référentiel et trouver vld-2.5.1-setup.exe dans les actifs


Certes, la dernière mise à jour date de Visual Studio 2015, mais elle fonctionne avec Visual Studio 2019. L'installation est standard, suivez simplement les instructions.


Pour activer VLD, vous devez enregistrer #include <vld.h> .


L'avantage de cet utilitaire est que vous ne pouvez pas l'exécuter en mode débogage (F5), car tout est affiché dans la console. Au tout début, cela sera affiché:


 Visual Leak Detector read settings from: C:\Program Files (x86)\Visual Leak Detector\vld.ini Visual Leak Detector Version 2.5.1 installed. 

Et voici ce qui se produira en cas de fuite de mémoire:


 WARNING: Visual Leak Detector detected memory leaks! ---------- Block 1 at 0x01405FD0: 376 bytes ---------- Leak Hash: 0x555D2B67, Count: 1, Total 376 bytes Call Stack (TID 8908): ucrtbased.dll!malloc() test.exe!0x00F41946() test.exe!0x00F42E1D() test.exe!0x00F44723() test.exe!0x00F44577() test.exe!0x00F4440D() test.exe!0x00F447A8() KERNEL32.DLL!BaseThreadInitThunk() + 0x19 bytes ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0xED bytes ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0xBD bytes Data: CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ Visual Leak Detector detected 1 memory leak (412 bytes). Largest number used: 3115 bytes. Total allocations: 3563 bytes. Visual Leak Detector is now exiting. 

Ici, je vois la trace! Alors, où sont les lignes de code? Où sont les noms des fonctions?



D'accord, la promesse a été tenue, mais ce n'est pas le résultat que je voulais.


Il reste une option que j'ai trouvée dans Google: un instantané de la mémoire. Cela se fait simplement: en mode débogage, lorsque vous arrivez à retourner 0, vous devez aller dans l'onglet "Utilisation de la mémoire" de l'outil de diagnostic et cliquer sur "Prendre un instantané". Peut-être que cette fonctionnalité sera désactivée pour vous, comme dans la première capture d'écran. Ensuite, vous devez activer et redémarrer le débogage.


Première capture d'écran


Deuxième capture d'écran


Après avoir pris la photo, vous verrez une taille sous le tas. Je pense que c'est la quantité de mémoire allouée pendant le programme. Cliquez sur cette taille. Nous aurons une fenêtre qui contiendra les objets qui sont stockés dans ce tas. Pour voir des informations détaillées, vous devez sélectionner un objet et cliquer sur le bouton "Instances de représentation d'objets Foo".


Troisième capture d'écran


Quatrième capture d'écran


Oui! C'est une victoire! Trace complète avec localisation de l'appel! C'est ce qui était à l'origine nécessaire.


Linux - développement
Voyons maintenant ce qui se passe sous Linux.

Sous Linux, l'utilitaire valgrind existe. Pour installer valgrind, vous devez enregistrer sudo apt install valgrind dans la console (pour la famille Debian).


J'ai écrit un petit programme qui remplit un tableau dynamique, mais en même temps, la mémoire n'est pas effacée:


main.c
 #include <stdlib.h> #include <stdio.h> #define N 10 int main() { int * mas = (int *)malloc(N * sizeof(int)); for(int i = 0; i < N; i++) { *(mas+i) = i; printf("%d\t", *(mas+i)); } printf("\n"); return 0; } 

Après avoir compilé le programme en utilisant CLang, nous obtenons un fichier .out, que nous lançons à valgrind.


Utilisation de la valgrind ./a.out . Comment fonctionne valgrind, je pense qu'il est logique de le décrire dans un article séparé, et maintenant, comment le programme s'exécute, valgrind affichera ceci:


 ==2342== HEAP SUMMARY: ==2342== in use at exit: 40 bytes in 1 blocks ==2342== total heap usage: 2 allocs, 1 frees, 1,064 bytes allocated ==2342== ==2342== Searching for pointers to 1 not-freed blocks ==2342== Checked 68,984 bytes ==2342== ==2342== LEAK SUMMARY: ==2342== definitely lost: 40 bytes in 1 blocks ==2342== indirectly lost: 0 bytes in 0 blocks ==2342== possibly lost: 0 bytes in 0 blocks ==2342== still reachable: 0 bytes in 0 blocks ==2342== suppressed: 0 bytes in 0 blocks ==2342== Rerun with --leak-check=full to see details of leaked memory ==2342== ==2342== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) ==2342== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) 

Ainsi, jusqu'à présent, valgrind montre combien de mémoire a été perdue. Pour voir où la mémoire a été allouée, vous devez écrire --leak-check=full , puis, valgrind, en plus de ce qui précède, affichera ceci:


 ==2348== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==2348== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==2348== by 0x40053A: main (in /home/hunterlan/Habr/a.out) 

Bien sûr, aucune chaîne n'est spécifiée ici, mais une fonction a déjà été spécifiée, ce qui est une bonne nouvelle.


Il existe des alternatives à valgrind, comme strace ou Dr.Memory, mais je ne les ai pas utilisées, et elles sont principalement utilisées lorsque valgrind est impuissant.


Conclusions


Je suis heureux d'avoir été confronté au problème de trouver une fuite de mémoire dans Visual Studio, car j'ai appris beaucoup de nouveaux outils, quand et comment les utiliser, et j'ai commencé à comprendre comment ces outils fonctionnent.


Merci de votre attention, bonne écriture de code pour vous!

Source: https://habr.com/ru/post/fr480368/


All Articles