¿Cómo uso valgrind para encontrar las pérdidas de memoria en un programa?
¿Alguien me puede ayudar y describir los pasos para llevar a cabo el procedimiento?
Estoy usando Ubuntu 10.04 y tengo un programa a.c
, por favor, ayúdenme.
¿Cómo uso valgrind para encontrar las pérdidas de memoria en un programa?
¿Alguien me puede ayudar y describir los pasos para llevar a cabo el procedimiento?
Estoy usando Ubuntu 10.04 y tengo un programa a.c
, por favor, ayúdenme.
Respuestas:
No para insultar al OP, sino para aquellos que vienen a esta pregunta y aún son nuevos en Linux: es posible que deba instalar Valgrind en su sistema.
sudo apt install valgrind # Ubuntu, Debian, etc.
sudo yum install valgrind # RHEL, CentOS, Fedora, etc.
Valgrind se puede usar fácilmente para el código C / C ++, pero incluso se puede usar para otros idiomas cuando se configura correctamente (ver esto para Python).
Para ejecutar Valgrind , pase el ejecutable como argumento (junto con cualquier parámetro al programa).
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
--log-file=valgrind-out.txt \
./executable exampleParam1
Las banderas son, en resumen:
--leak-check=full
: "cada fuga individual se mostrará en detalle"--show-leak-kinds=all
: Mostrar todos los tipos de fuga "definidos, indirectos, posibles y alcanzables" en el informe "completo".--track-origins=yes
: Favorece la salida útil sobre la velocidad. Esto rastrea los orígenes de los valores no inicializados, lo que podría ser muy útil para los errores de memoria. Considere apagar si Valgrind es inaceptablemente lento.--verbose
: Puede informarle sobre el comportamiento inusual de su programa. Repita para más verbosidad.--log-file
: Escribe en un archivo. Útil cuando la salida excede el espacio terminal.Finalmente, le gustaría ver un informe Valgrind que se vea así:
HEAP SUMMARY:
in use at exit: 0 bytes in 0 blocks
total heap usage: 636 allocs, 636 frees, 25,393 bytes allocated
All heap blocks were freed -- no leaks are possible
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Entonces, tiene una pérdida de memoria y Valgrind no dice nada significativo. Quizás, algo como esto:
5 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x40053E: main (in /home/Peri461/Documents/executable)
Echemos un vistazo al código C que escribí también:
#include <stdlib.h>
int main() {
char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
return 0;
}
Bueno, hubo 5 bytes perdidos. ¿Como paso? El informe de error solo dice
main
y malloc
. En un programa más grande, sería muy problemático perseguirlo. Esto se debe a cómo se compiló el ejecutable . De hecho, podemos obtener detalles línea por línea sobre lo que salió mal. Vuelva a compilar su programa con un indicador de depuración (lo estoy usando gcc
aquí):
gcc -o executable -std=c11 -Wall main.c # suppose it was this at first
gcc -o executable -std=c11 -Wall -ggdb3 main.c # add -ggdb3 to it
Ahora con esta construcción de depuración, Valgrind apunta a la línea exacta de código que asigna la memoria que se filtró. (La redacción es importante: puede que no sea exactamente dónde está la fuga, sino lo que se filtró. La traza lo ayuda a encontrar dónde ).
5 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x40053E: main (main.c:4)
IndexOutOfBoundsException
problemas de tipo.A veces, sus fugas / errores se pueden vincular entre sí, al igual que un IDE que descubre que aún no ha escrito un corchete de cierre. Resolver un problema puede resolver otros, así que busque uno que parezca un buen culpable y aplique algunas de estas ideas:
gdb
quizás) y busque errores de precondición / postcondición. La idea es rastrear la ejecución de su programa mientras se enfoca en la vida útil de la memoria asignada.60 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C2BB78: realloc (vg_replace_malloc.c:785)
by 0x4005E4: resizeArray (main.c:12)
by 0x40062E: main (main.c:19)
Y el código:
#include <stdlib.h>
#include <stdint.h>
struct _List {
int32_t* data;
int32_t length;
};
typedef struct _List List;
List* resizeArray(List* array) {
int32_t* dPtr = array->data;
dPtr = realloc(dPtr, 15 * sizeof(int32_t)); //doesn't update array->data
return array;
}
int main() {
List* array = calloc(1, sizeof(List));
array->data = calloc(10, sizeof(int32_t));
array = resizeArray(array);
free(array->data);
free(array);
return 0;
}
Como asistente de enseñanza, he visto este error a menudo. El alumno utiliza una variable local y se olvida de actualizar el puntero original. El error aquí es notar que en realloc
realidad puede mover la memoria asignada a otro lugar y cambiar la ubicación del puntero. Luego nos vamos resizeArray
sin decir a
array->data
dónde se movió la matriz.
1 errors in context 1 of 1:
Invalid write of size 1
at 0x4005CA: main (main.c:10)
Address 0x51f905a is 0 bytes after a block of size 26 alloc'd
at 0x4C2B975: calloc (vg_replace_malloc.c:711)
by 0x400593: main (main.c:5)
Y el código:
#include <stdlib.h>
#include <stdint.h>
int main() {
char* alphabet = calloc(26, sizeof(char));
for(uint8_t i = 0; i < 26; i++) {
*(alphabet + i) = 'A' + i;
}
*(alphabet + 26) = '\0'; //null-terminate the string?
free(alphabet);
return 0;
}
Tenga en cuenta que Valgrind nos señala la línea de código comentada arriba. La matriz de tamaño 26 está indexada [0,25], por lo que *(alphabet + 26)
es una escritura no válida, está fuera de los límites. Una escritura no válida es un resultado común de errores fuera de uno. Mire el lado izquierdo de su operación de asignación.
1 errors in context 1 of 1:
Invalid read of size 1
at 0x400602: main (main.c:9)
Address 0x51f90ba is 0 bytes after a block of size 26 alloc'd
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x4005E1: main (main.c:6)
Y el código:
#include <stdlib.h>
#include <stdint.h>
int main() {
char* destination = calloc(27, sizeof(char));
char* source = malloc(26 * sizeof(char));
for(uint8_t i = 0; i < 27; i++) {
*(destination + i) = *(source + i); //Look at the last iteration.
}
free(destination);
free(source);
return 0;
}
Valgrind nos señala la línea comentada arriba. Mira la última iteración aquí, que es
*(destination + 26) = *(source + 26);
. Sin embargo, *(source + 26)
está fuera de límites nuevamente, de manera similar a la escritura no válida. Las lecturas no válidas también son un resultado común de errores fuera de uno. Mire el lado derecho de su operación de asignación.
¿Cómo sé cuándo la fuga es mía? ¿Cómo encuentro mi fuga cuando estoy usando el código de otra persona? Encontré una fuga que no es mía; debería hacer algo? Todas son preguntas legítimas. Primero, 2 ejemplos del mundo real que muestran 2 clases de encuentros comunes.
#include <jansson.h>
#include <stdio.h>
int main() {
char* string = "{ \"key\": \"value\" }";
json_error_t error;
json_t* root = json_loads(string, 0, &error); //obtaining a pointer
json_t* value = json_object_get(root, "key"); //obtaining a pointer
printf("\"%s\" is the value field.\n", json_string_value(value)); //use value
json_decref(value); //Do I free this pointer?
json_decref(root); //What about this one? Does the order matter?
return 0;
}
Este es un programa simple: lee una cadena JSON y la analiza. En la fabricación, utilizamos llamadas a la biblioteca para hacer el análisis por nosotros. Jansson realiza las asignaciones necesarias dinámicamente ya que JSON puede contener estructuras anidadas de sí mismo. Sin embargo, esto no significa que nosotros decref
o "liberemos" la memoria que se nos da de cada función. De hecho, este código que escribí anteriormente arroja tanto una "Lectura no válida" como una "Escritura no válida". Esos errores desaparecen cuando saca la decref
línea value
.
¿Por qué? La variable value
se considera una "referencia prestada" en la API de Jansson. Jansson realiza un seguimiento de su memoria para usted, y simplemente tiene que decref
JSON estructuras independientes entre sí. La lección aquí:
lea la documentación . De Verdad. A veces es difícil de entender, pero te dicen por qué suceden estas cosas. En cambio, tenemos
preguntas existentes sobre este error de memoria.
#include "SDL2/SDL.h"
int main(int argc, char* argv[]) {
if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
return 1;
}
SDL_Quit();
return 0;
}
¿Qué hay de malo con este código ? Constantemente pierde ~ 212 KiB de memoria para mí. Tómese un momento para pensarlo. Activamos y desactivamos SDL. ¿Responder? No hay nada malo.
Eso puede sonar extraño al principio . A decir verdad, los gráficos son desordenados y, a veces, debe aceptar algunas fugas como parte de la biblioteca estándar. La lección aquí: no necesita reprimir cada pérdida de memoria . A veces solo necesita suprimir las filtraciones porque son problemas conocidos sobre los que no puede hacer nada . (¡Este no es mi permiso para ignorar sus propias filtraciones!)
¿Cómo sé cuándo la fuga es mía?
Es. (99% seguro, de todos modos)
¿Cómo encuentro mi fuga cuando estoy usando el código de otra persona?
Lo más probable es que alguien más lo haya encontrado. ¡Prueba Google! Si eso falla, usa las habilidades que te di anteriormente. Si eso falla y la mayoría de las veces ve llamadas de API y poco de su propio seguimiento de pila, vea la siguiente pregunta.
Encontré una fuga que no es mía; debería hacer algo?
¡Si! La mayoría de las API tienen formas de informar errores y problemas. ¡Usalos, usalos a ellos! ¡Ayuda a devolver las herramientas que estás usando en tu proyecto!
Gracias por quedarte conmigo tanto tiempo. Espero que hayas aprendido algo, ya que intenté atender al amplio espectro de personas que llegaban a esta respuesta. Algunas cosas espero que haya preguntado en el camino: ¿Cómo funciona el asignador de memoria de C? ¿Qué es realmente una pérdida de memoria y un error de memoria? ¿Cómo son diferentes de segfaults? ¿Cómo funciona Valgrind? Si tienes alguno de estos, alimenta tu curiosidad:
memcheck
herramienta está habilitada por defecto?
memcheck
es la herramienta predeterminada:--tool=<toolname> [default: memcheck]
Prueba esto:
valgrind --leak-check=full -v ./your_program
Mientras valgrind esté instalado, revisará su programa y le dirá qué está mal. Puede darle consejos y lugares aproximados donde se pueden encontrar sus fugas. Si está segfault'ing, intente ejecutarlo gdb
.
your_program
== el nombre del ejecutable o cualquier comando que use para ejecutar su aplicación.
Tu puedes correr:
valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
Puede crear un alias en el archivo .bashrc de la siguiente manera
alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'
Por lo tanto, cada vez que desee verificar pérdidas de memoria, simplemente haga
vg ./<name of your executable> <command line parameters to your executable>
Esto generará un archivo de registro Valgrind en el directorio actual.