
Los saludo, khabrovchianos!
Hoy quiero abrir un poco de luz sobre cómo lidiar con la pérdida de memoria en C o C ++.
Ya hay dos artículos sobre Habré, a saber: Nos ocupamos de las pérdidas de memoria (C ++ CRT) y las pérdidas de memoria en C ++: Detector de fugas visuales . Sin embargo, creo que no se divulgan lo suficiente, o estos métodos pueden no dar el resultado que necesita, por lo que me gustaría conocer en la medida de lo posible todos los métodos disponibles para facilitarle la vida.
Windows - desarrollo
Comencemos con Windows, es decir, el desarrollo para Visual Studio, ya que la mayoría de los programadores novatos escriben específicamente para este IDE.

Para entender lo que está sucediendo, aplico un ejemplo 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:
Y también hay Student.h
y Student.c
en el que se declaran estructuras y funciones.
Hay una tarea: demostrar la ausencia de pérdidas de memoria. Lo primero que viene a la mente es la CRT. Aquí todo es bastante simple.
En la parte superior del archivo donde se encuentra main, agregue este código:
#define __CRTDBG_MAP_ALLOC #include <crtdbg.h> #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__) #define new DEBUG_NEW
Y antes de return 0
, debe registrar esto: _CrtDumpMemoryLeaks();
.
Como resultado, en modo de depuración, el estudio generará esto:
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.
Genial Ahora sabe que tiene una pérdida de memoria. Ahora necesita eliminar esto, por lo que solo necesita averiguar dónde olvidamos borrar la memoria. Y aquí surge un problema: ¿dónde, de hecho, se asignó esta memoria?

Después de repetir todos los pasos, descubrí que la memoria se pierde en algún lugar aquí:
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); }
Pero como es eso? ¿Estoy liberando todo? O no?
Y aquí realmente extrañé a Valgrind, con su rastreo de llamadas ...
Como resultado, después de 15 minutos de caminata, encontré un análogo de Valgrind - Visual Leak Detector . Esta es una biblioteca de terceros, un contenedor sobre CRT que prometió mostrar el seguimiento. Esto es lo que necesito.
Para instalarlo, debe ir al repositorio y encontrar vld-2.5.1-setup.exe
en los activos
Es cierto que la última actualización fue de la época de Visual Studio 2015, pero funciona con Visual Studio 2019. La instalación es estándar, solo siga las instrucciones.
Para habilitar VLD, debe registrar #include <vld.h>
.
La ventaja de esta utilidad es que no puede ejecutarse en modo de depuración (F5), porque todo se muestra en la consola. Al principio, esto se mostrará:
Visual Leak Detector read settings from: C:\Program Files (x86)\Visual Leak Detector\vld.ini Visual Leak Detector Version 2.5.1 installed.
Y esto es lo que producirá cuando una pérdida de memoria:
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.
¡Aquí veo el rastro! Entonces, ¿dónde están las líneas de código? ¿Dónde están los nombres de las funciones?

De acuerdo, la promesa se cumplió, pero este no es el resultado que quería.
Queda una opción que encontré en Google: una instantánea de la memoria. Se hace simplemente: en modo de depuración, cuando puede devolver 0, debe ir a la pestaña "Uso de memoria" en la herramienta de diagnóstico y hacer clic en "Tomar instantánea". Quizás esta función se deshabilite para usted, como en la primera captura de pantalla. Luego debe encender y reiniciar la depuración.
Primera captura de pantalla Segunda captura de pantalla Después de tomar la foto, verá un tamaño debajo del montón. Creo que esta es la cantidad de memoria asignada durante el programa. Haga clic en este tamaño. Tendremos una ventana que contendrá los objetos que están almacenados en este montón. Para ver información detallada, debe seleccionar un objeto y hacer clic en el botón "Instancias de representación de objetos Foo".
Tercera captura de pantalla Cuarta captura de pantalla Si! Esta es una victoria! Seguimiento completo con la ubicación de la llamada! Esto es lo que originalmente se necesitaba.
Linux - desarrollo
Ahora, veamos qué sucede en Linux.

En Linux, existe la utilidad valgrind. Para instalar valgrind, debe registrar sudo apt install valgrind
en la consola (para la familia Debian).
Escribí un pequeño programa que llena una matriz dinámica, pero al mismo tiempo, la memoria no se borra:
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; }
Después de compilar el programa usando CLang, obtenemos un archivo .out, que lanzamos para valgrind.
Usando el valgrind ./a.out
. Cómo funciona valgrind, creo que tiene sentido describirlo en un artículo separado, y ahora, cómo se ejecuta el programa, valgrind generará esto:
==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)
Por lo tanto, valgrind hasta ahora muestra cuánta memoria se ha perdido. Para ver dónde se asignó la memoria, debe escribir --leak-check=full
, y luego, valgrind, además de lo anterior, generará esto:
==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)
Por supuesto, no hay una cadena especificada aquí, pero ya se ha especificado una función, lo cual es una buena noticia.
Hay alternativas a valgrind, como strace o Dr.Memory, pero no las utilicé, y se usan principalmente cuando valgrind es impotente.
Conclusiones
Me alegro de haber enfrentado el problema de encontrar una pérdida de memoria en Visual Studio, ya que aprendí muchas herramientas nuevas, cuándo y cómo usarlas, y comencé a entender cómo funcionan estas herramientas.
¡Gracias por su atención, buen código escrito para usted!