¿Cómo uso valgrind para encontrar pérdidas de memoria?

181

¿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.

usuario484457
fuente
16
Utiliza valgrind para probar su programa compilado , no el código fuente.
Tony
66
La respuesta dada a continuación por @RageD es correcta, ¿por qué no la acepta?
Pratik Singhal
1
Una fuga es causada por algo que usted no puede hacer, es decir. memoria asignada libre. Por lo tanto, Valgrind no puede mostrarle "dónde" está la pérdida, solo usted sabe dónde ya no se necesita la memoria asignada. Sin embargo, al decirle qué asignación no es gratuita () d, al rastrear el uso de esa memoria a través de su programa, debería poder determinar dónde debería liberarse () d. Un error común es salir de una función sin liberar memoria asignada.
MikeW
1
Relacionado: con cualquier herramienta: stackoverflow.com/questions/6261201/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

296

Cómo ejecutar Valgrind

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)

Tengo una fuga, pero ¿ DÓNDE ?

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 mainy 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 gccaquí):

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)

Técnicas para depurar fugas y errores de memoria

  • ¡Utilice www.cplusplus.com ! Tiene una excelente documentación sobre las funciones de C / C ++.
  • Consejos generales para pérdidas de memoria:
    • Asegúrese de que su memoria asignada dinámicamente se libere.
    • No asigne memoria y olvide asignar el puntero.
    • No sobrescriba un puntero con uno nuevo a menos que se libere la memoria anterior.
  • Consejos generales para errores de memoria:
    • Acceda y escriba en direcciones e índices que seguramente le pertenezcan. Los errores de memoria son diferentes de las fugas; a menudo son solo IndexOutOfBoundsException problemas de tipo.
    • No acceda ni escriba en la memoria después de liberarlo.
  • 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:

    • Enumere las funciones en su código que dependen / dependen del código "ofensivo" que tiene el error de memoria. Siga la ejecución del programa (quizás incluso en gdbquizá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.
    • Intente comentar el bloque de código "ofensivo" (dentro de lo razonable, para que su código aún se compile). Si el error Valgrind desaparece, has encontrado dónde está.
  • Si todo lo demás falla, intente buscarlo. ¡Valgrind también tiene documentación !

Una mirada a las fugas y errores comunes

Mira tus punteros

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 reallocrealidad puede mover la memoria asignada a otro lugar y cambiar la ubicación del puntero. Luego nos vamos resizeArraysin decir a array->datadónde se movió la matriz.

Escritura inválida

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.

Lectura inválida

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.


La toppia de código abierto (U / Dys)

¿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.

Jansson : una biblioteca JSON

#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 decrefo "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 decreflínea value.

¿Por qué? La variable valuese 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.

SDL : una biblioteca de gráficos y juegos

#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!)

Respuestas al vacío

¿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!


Otras lecturas

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:

Joshua Detwiler
fuente
44
Mucho mejor respuesta, una pena que esta no sea la respuesta aceptada.
A. Smoliak
Creo que es una buena práctica hacer tal cosa, hice algunas yo mismo
A. Smoliak
1
¿Puedo destacar esta respuesta y usarla como referencia futura para mí? ¡Buen trabajo!
Zap
¿La memcheckherramienta está habilitada por defecto?
abhiarora
@abhiarora Sí. La página del manual nos dice que esa memcheckes la herramienta predeterminada:--tool=<toolname> [default: memcheck]
Joshua Detwiler
146

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.

RageD
fuente
¿Qué significa "your_program"? ¿Es esta la ubicación del código fuente o el nombre de la aplicación, como el archivo apk?
HoangVu
77
your_program== el nombre del ejecutable o cualquier comando que use para ejecutar su aplicación.
RageD
27

Tu puedes correr:

valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
Rajat Paliwal
fuente
1

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.

Sachin Rastogi
fuente