
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.cstruct 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:
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.
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".
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ê!