Captura de vazamentos de memória em C / C ++


Saúdo-vos, Khabrovchians!


Hoje eu quero abrir um pouco de luz sobre como lidar com vazamento de memória em C ou C ++.


Já existem dois artigos sobre Habré, a saber: Lidamos com vazamentos de memória (C ++ CRT) e vazamentos de memória em C ++: Visual Leak Detector . No entanto, acredito que eles não são suficientemente divulgados ou que esses métodos podem não fornecer o resultado que você precisa, portanto, gostaria de esclarecer, na medida do possível, todos os métodos disponíveis para facilitar sua vida.


Windows - desenvolvimento
Vamos começar com o Windows, ou seja, o desenvolvimento para o Visual Studio, pois a maioria dos programadores iniciantes escreve especificamente para esse IDE.



Para entender o que está acontecendo, aplico um exemplo real:


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; } 

E também há Student.h Student.c em que estruturas e funções são declaradas.


Há uma tarefa: demonstrar a ausência de vazamentos de memória. A primeira coisa que vem à mente é a CRT. Tudo é bem simples aqui.


Na parte superior do arquivo em que main está localizado, adicione este pedaço de código:


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

E antes do return 0 você precisa registrar isso: _CrtDumpMemoryLeaks(); .


Como resultado, no modo Debug, o estúdio exibirá isso:


 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. 

Ótimo! Agora você sabe que tem um vazamento de memória. Agora você precisa eliminar isso, para descobrir onde esquecemos de limpar a memória. E aqui surge um problema: onde, de fato, essa memória foi alocada?



Depois de repetir todas as etapas, descobri que a memória está perdida em algum lugar aqui:


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

Mas como é isso? Estou liberando tudo? Ou não?


E aqui eu realmente senti falta de Valgrind, com seu rastreamento de chamadas ...


Como resultado, após 15 minutos de caminhada, encontrei um análogo do Valgrind - Visual Leak Detector . Esta é uma biblioteca de terceiros, um invólucro do CRT que prometeu mostrar o rastreamento! É disso que eu preciso.


Para instalá-lo, você precisa ir ao repositório e encontrar vld-2.5.1-setup.exe nos ativos


É verdade que a última atualização foi do tempo do Visual Studio 2015, mas funciona com o Visual Studio 2019. A instalação é padrão, basta seguir as instruções.


Para habilitar o VLD, você deve registrar #include <vld.h> .


A vantagem desse utilitário é que você não pode executar no modo de depuração (F5), porque tudo é exibido no console. No início, isso será exibido:


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

E aqui está o que produzirá quando um vazamento de memória:


 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. 

Aqui, eu vejo o traço! Então, onde estão as linhas de código? Onde estão os nomes das funções?



Ok, a promessa foi cumprida, mas este não é o resultado que eu queria.


Ainda existe uma opção que encontrei no Google: um instantâneo da memória. Isso é feito de maneira simples: no modo de depuração, quando você retornar 0, precisará ir para a guia "Uso da memória" na ferramenta de diagnóstico e clicar em "Obter instantâneo". Talvez esse recurso esteja desativado para você, como na primeira captura de tela. Então você precisa ativar e reiniciar a depuração.


Primeira captura de tela


Segunda captura de tela


Depois de tirar a foto, você verá um tamanho embaixo da pilha. Eu acho que isso é quanta memória foi alocada durante o programa. Clique neste tamanho. Teremos uma janela que conterá objetos armazenados nesta pilha. Para ver informações detalhadas, você precisa selecionar um objeto e clicar no botão "Instâncias de representação de objetos Foo".


Terceira captura de tela


Quarta captura de tela


Sim Isto é uma vitória! Rastreamento completo com localização da chamada! Isto é o que era originalmente necessário.


Linux - desenvolvimento
Agora, vamos ver o que acontece no Linux.

No Linux, o utilitário valgrind existe. Para instalar o valgrind, você precisa registrar o sudo apt install valgrind no console (para a família Debian).


Eu escrevi um pequeno programa que preenche uma matriz dinâmica, mas ao mesmo tempo, a memória não é limpa:


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; } 

Após compilar o programa usando CLang, obtemos um arquivo .out, que lançamos para o valgrind.


Usando o valgrind ./a.out . Como o valgrind funciona, acho que faz sentido descrevê-lo em um artigo separado e, agora, como o programa é executado, o valgrind exibirá isso:


 ==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) 

Assim, o valgrind até agora mostra quanta memória foi perdida. Para ver onde a memória foi alocada, você precisa registrar --leak-check=full e, em seguida, o valgrind, além do acima, produzirá o seguinte:


 ==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) 

Obviamente, não há nenhuma sequência especificada aqui, mas uma função já foi especificada, o que é uma boa notícia.


Existem alternativas ao valgrind, como strace ou Dr.Memory, mas eu não as usei e elas são usadas principalmente onde o valgrind é impotente.


Conclusões


Fico feliz por ter encontrado o problema de encontrar um vazamento de memória no Visual Studio, pois aprendi muitas novas ferramentas, quando e como usá-las, e comecei a entender como essas ferramentas funcionam.


Obrigado pela atenção, bom código de escrita para você!

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


All Articles