Tutorial de Valgrind

O Valgrind é uma excelente ferramenta para resolver dois problemas em seus programas: vazamento de memória e acesso a posições inválidas de memória (o que pode levar a segmentation fault).

A seguir apresentamos alguns exemplos de como usar o Valgrind.

Considere o seguinte programa que lê 10 números e depois imprime-os na ordem inversa.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char const *argv[]) {
  int i, *vetor = malloc(10 * sizeof(int));
  for (i = 0; i < 10; i++)
    scanf("%d", &vetor[i]);
  for (i = 9; i >= 0; i--)
    printf("%d\n", vetor[i]);
  return 0;
}

Esse programa tem um vazamento de memória: o vetor alocado não é desalocado antes do programa terminar.

Isso pode ser detectado com o Valgrind. Suponha que o arquivo compilado chama programa e que você tem um arquivo de teste chamado entrada (como os fornecidos no SuSy).

No terminal, execute:

valgrind --leak-check=full ./programa < entrada**

Você verá o seguinte resultado.

==1918== Memcheck, a memory error detector
==1918== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==1918== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==1918== Command: ./programa
==1918==

...

==1918==
==1918== HEAP SUMMARY:
==1918==     in use at exit: 40 bytes in 1 blocks
==1918==   total heap usage: 3 allocs, 2 frees, 5,160 bytes allocated
==1918==
==1918== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==1918==    at 0x4C2BBAF: malloc (vg_replace_malloc.c:299)
==1918==    by 0x108758: main (programa.c:5)
==1918==
==1918== LEAK SUMMARY:
==1918==    definitely lost: 40 bytes in 1 blocks
==1918==    indirectly lost: 0 bytes in 0 blocks
==1918==      possibly lost: 0 bytes in 0 blocks
==1918==    still reachable: 0 bytes in 0 blocks
==1918==         suppressed: 0 bytes in 0 blocks
==1918==
==1918== For counts of detected and suppressed errors, rerun with: -v
==1918== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

No começo o Valgrind dá algumas mensagens padrões, depois vemos a saída do programa (substituído aqui por …) e depois o Valgrind avisa que houveram bytes perdidos na seções HEAP SUMMARY e LEAK SUMMARY. Perdemos 40 bytes em de um bloco (uma chamada de malloc). Mais do que isso, eles avisa que o malloc responsável pelo vazamento foi no arquivo programa.c, na linha 5 (programa.c:5).

Suponha que você corrija o código, liberando o vetor:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char const *argv[]) {
  int i, *vetor = malloc(10 * sizeof(int));
  for (i = 0; i < 10; i++)
    scanf("%d", &vetor[i]);
  for (i = 9; i >= 0; i--)
    printf("%d\n", vetor[i]);
  free(vetor);
  return 0;
}

Então a saída do valgrind será:

==1931== Memcheck, a memory error detector
==1931== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==1931== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==1931== Command: ./programa
==1931==

...

==1931==
==1931== HEAP SUMMARY:
==1931==     in use at exit: 0 bytes in 0 blocks
==1931==   total heap usage: 3 allocs, 3 frees, 5,160 bytes allocated
==1931==
==1931== All heap blocks were freed -- no leaks are possible
==1931==
==1931== For counts of detected and suppressed errors, rerun with: -v
==1931== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Ou seja, nesse caso não há vazamento de memória!

O Valgrind também avisa de erros de acesso a posições inválidas. Por exemplo, o seguinte programa tenta escrever o número 1 na posição 0 do vetor, mas o vetor começa como NULL, isto é, tentamos escrever na posição 0x0 (NULL) da memória.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char const *argv[]) {
  int *vetor = NULL;
  vetor[0] = 1;
  return 0;
}

Neste caso, o Valgrind nos avisa que estamos fazendo uma escrita ilegal na linha 6 do programa.c. Assim é possível saber o que causou o segmentation fault.

==1990== Memcheck, a memory error detector
==1990== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==1990== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==1990== Command: ./programa
==1990==
==1990== Invalid write of size 4
==1990==    at 0x108677: main (programa.c:6)
==1990==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==1990==
==1990==
==1990== Process terminating with default action of signal 11 (SIGSEGV)
==1990==  Access not within mapped region at address 0x0
==1990==    at 0x108677: main (programa.c:6)
==1990==  If you believe this happened as a result of a stack
==1990==  overflow in your program's main thread (unlikely but
==1990==  possible), you can try to increase the size of the
==1990==  main thread stack using the --main-stacksize= flag.
==1990==  The main thread stack size used in this run was 8388608.
==1990==
==1990== HEAP SUMMARY:
==1990==     in use at exit: 0 bytes in 0 blocks
==1990==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==1990==
==1990== All heap blocks were freed -- no leaks are possible
==1990==
==1990== For counts of detected and suppressed errors, rerun with: -v
==1990== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Segmentation fault

Veja outro exemplo, onde alocamos um vetor de 8 posições, mas tentamos usar até 10 posições.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char const *argv[]) {
  int i, *vetor = malloc(8 * sizeof(int));
  for (i = 0; i < 10; i++)
    scanf("%d", &vetor[i]);
  for (i = 9; i >= 0; i--)
    printf("%d\n", vetor[i]);
  free(vetor);
  return 0;
}

A saída do valgrind indica o problema da escrita na posição errada do vetor linha 7 (invalid write of size 4), indicando onde o bloco de memória mais próximo foi alocado (um bloco de 32 bytes alocado em programa.c:5) e indica também o problema da leitura na posição errada (invalid read of size 4).

==1955== Memcheck, a memory error detector
==1955== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==1955== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==1955== Command: ./programa
==1955==
==1955== Invalid write of size 4
==1955==    at 0x4E91794: _IO_vfscanf (vfscanf.c:1902)
==1955==    by 0x4E9C23A: scanf (scanf.c:33)
==1955==    by 0x1087CD: main (programa.c:7)
==1955==  Address 0x51d7060 is 0 bytes after a block of size 32 alloc'd
==1955==    at 0x4C2BBAF: malloc (vg_replace_malloc.c:299)
==1955==    by 0x108798: main (programa.c:5)
==1955==
==1955== Invalid read of size 4
==1955==    at 0x1087F5: main (programa.c:9)
==1955==  Address 0x51d7064 is 4 bytes after a block of size 32 alloc'd
==1955==    at 0x4C2BBAF: malloc (vg_replace_malloc.c:299)
==1955==    by 0x108798: main (programa.c:5)
==1955==

...

==1955==
==1955== HEAP SUMMARY:
==1955==     in use at exit: 0 bytes in 0 blocks
==1955==   total heap usage: 3 allocs, 3 frees, 5,152 bytes allocated
==1955==
==1955== All heap blocks were freed -- no leaks are possible
==1955==
==1955== For counts of detected and suppressed errors, rerun with: -v
==1955== ERROR SUMMARY: 4 errors from 2 contexts (suppressed: 0 from 0)