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