Valgrind como detector de vazamento de memória

O conjunto de ferramentas Valgrind fornece uma série de recursos de depuração e profiling que ajudam a tornar os programas desenvolvidos mais rápidos e corretos. A mais popular dessas ferramentas é chamada Memcheck. Esta pode detectar muitos erros relacionados à memória que são comuns em programas C/C++ e que podem levar a falhas e comportamentos imprevisíveis. Os outros recursos são, Cachegrind, Callgrind, Massif, Helgrind, DRD e DHAT. Os comandos escritos aqui neste post são bem simplificados. Caso queiram mais informações bom ler o manual do usuário disponível aqui.

Preparando o ambiente

Compile o programa com a opção -g para incluir informações de depuração. Desta forma o Memcheck irá mostrar a linha exata do erro. Quanto a otimização coloque -O0, mas nem sempre em um código muito extenso deverá utilizar esta opção. Uma solução é colocar -O1 já que tem resultado significativamente bons.

Executando o programa

A forma de execução do programa com a ferramenta é:

valgrind --leak-check=yes ./meu_programa <argumentos>

O programa de exemplo será o código a seguir (main.c):

#include <stdlib.h>
#include <unistd.h>

int main(void){
    char* arg1 = malloc(10);
    int* arg2 = malloc(sizeof(int));
    write (1 /* stdout */, arg1, 10);
    exit(arg2[0]); 
    return 0;
}

Para compilar digite em um terminal:

gcc -o memcheck.exe main.c -O0 -Wall -pg -g

Muitas mensagens aparecem depois que executa o comando, parecida com as mensagens a seguir:

$valgrind --leak-check=yes ./memcheck.exe
==8269== Memcheck, a memory error detector
==8269== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8269== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==8269== Command: ./memcheck.exe
==8269== 
==8269== Syscall param write(buf) points to uninitialised byte(s)
==8269==    at 0x498BA37: write (write.c:26)
==8269==    by 0x10928C: main (main.c:10)
==8269==  Address 0x4aa40e0 is 0 bytes inside a block of size 10 alloc'd
==8269==    at 0x4848899: malloc (vg_replace_malloc.c:381)
==8269==    by 0x109264: main (main.c:8)
==8269== 
==8269== Syscall param exit_group(status) contains uninitialised byte(s)
==8269==    at 0x4961CA1: _Exit (_exit.c:30)
==8269==    by 0x48BC551: __run_exit_handlers (exit.c:136)
==8269==    by 0x48BC60F: exit (exit.c:143)
==8269==    by 0x109299: main (main.c:11)
==8269== 
==8269== 
==8269== HEAP SUMMARY:
==8269==     in use at exit: 14 bytes in 2 blocks
==8269==   total heap usage: 3 allocs, 1 frees, 8,302 bytes allocated
==8269== 
==8269== LEAK SUMMARY:
==8269==    definitely lost: 0 bytes in 0 blocks
==8269==    indirectly lost: 0 bytes in 0 blocks
==8269==      possibly lost: 0 bytes in 0 blocks
==8269==    still reachable: 14 bytes in 2 blocks
==8269==         suppressed: 0 bytes in 0 blocks
==8269== Reachable blocks (those to which a pointer was found) are not shown.
==8269== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==8269== 
==8269== Use --track-origins=yes to see where uninitialised values come from
==8269== For lists of detected and suppressed errors, rerun with: -s
==8269== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

Analisando as mensagens

  • O primeiro problema é o bloco da área de heap da alocação de memória está além do permitido.
  • O código ==8269== é o PID do processo (identificador). Importante quando quer saber se tem algum problema com prioridades.
  • A primeira linha, (“Syscall param write(buf) points to uninitialised…”), mostra o tipo de erro.
  • O endereço do código onde ocorre o erro é mostrado em 0x498BA37 e qual a linha no código em C.
  • Também mostra quantos blocos forma utilizados para o programa. No exemplo, 14 bytes em 2 blocos.
  • Existe um erro de vazamento de memória descrito em (“0x4848899: malloc (vg_replace_malloc.c:381)”).
  • Veja que inclusive a ferramenta sugere a opção –leak-check=full para uma verificação mais completa e para mostrar todos os erros a opção -s.

Podemos modificar o código anterior para que o mesmo possa gerar um vazamento de memória. Para isso modifique a linha int* arg2 = malloc(sizeof(int)); para int* arg2 = malloc(10 * sizeof(int));

Também inclua a linha arg2[10] = 0; e retire a linha exit(arg2[0]);

Veja o que ocorre:

==8923== Invalid write of size 4
==8923==    at 0x10925F: main (main.c:10)
==8923==  Address 0x4aa4148 is 0 bytes after a block of size 40 alloc'd
==8923==    at 0x4848899: malloc (vg_replace_malloc.c:381)
==8923==    by 0x109252: main (main.c:9)
...
...
...
==8923== 10 bytes in 1 blocks are definitely lost in loss record 1 of 2
==8923==    at 0x4848899: malloc (vg_replace_malloc.c:381)
==8923==    by 0x109244: main (main.c:8)
==8923== 
==8923== 40 bytes in 1 blocks are definitely lost in loss record 2 of 2
==8923==    at 0x4848899: malloc (vg_replace_malloc.c:381)
==8923==    by 0x109252: main (main.c:9)
==8923== 
==8923== LEAK SUMMARY:
==8923==    definitely lost: 50 bytes in 2 blocks
==8923==    indirectly lost: 0 bytes in 0 blocks
==8923==      possibly lost: 0 bytes in 0 blocks
...
...
...

Como a memória não foi desalocada e o tamanho excedeu a alocação do heap, existe um vazamento de 50 bytes em dois blocos.

  • Onde aparece “definitely lost…”: seu programa está vazando memória. Deve ser corrigido.
  • “probably lost”: seu programa está vazando memória se você utilizar os ponteiros de forma indevida (mover o ponteiro até o meio do bloco do heap).

Leave a Reply

Your email address will not be published. Required fields are marked *