Como uso o valgrind para encontrar vazamentos de memória em um programa?
Por favor, alguém me ajude e descreva as etapas para realizar o procedimento?
Estou usando o Ubuntu 10.04 e tenho um programa a.c
, por favor me ajude.
Como uso o valgrind para encontrar vazamentos de memória em um programa?
Por favor, alguém me ajude e descreva as etapas para realizar o procedimento?
Estou usando o Ubuntu 10.04 e tenho um programa a.c
, por favor me ajude.
Respostas:
Não para insultar o OP, mas para aqueles que chegam a essa pergunta e ainda são novos no Linux - talvez seja necessário instalar o Valgrind no seu sistema.
sudo apt install valgrind # Ubuntu, Debian, etc.
sudo yum install valgrind # RHEL, CentOS, Fedora, etc.
O Valgrind é facilmente utilizável para código C / C ++, mas pode até ser usado para outras linguagens quando configurado corretamente (veja isto em Python).
Para executar o Valgrind , passe o executável como argumento (junto com quaisquer parâmetros ao programa).
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
--log-file=valgrind-out.txt \
./executable exampleParam1
As bandeiras são, em resumo:
--leak-check=full
: "cada vazamento individual será mostrado em detalhes"--show-leak-kinds=all
: Mostre todos os tipos de vazamento "definidos, indiretos, possíveis e acessíveis" no relatório "completo".--track-origins=yes
: Favorece a saída útil acima da velocidade. Isso rastreia as origens de valores não inicializados, o que pode ser muito útil para erros de memória. Considere desligar se Valgrind estiver inaceitavelmente lento.--verbose
: Pode falar sobre o comportamento incomum do seu programa. Repita para mais detalhes.--log-file
: Escreva para um arquivo. Útil quando a saída excede o espaço do terminal.Por fim, você gostaria de ver um relatório Valgrind parecido com este:
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)
Então, você tem um vazamento de memória e Valgrind não está dizendo nada significativo. Talvez algo como isto:
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)
Vamos dar uma olhada no código C que escrevi também:
#include <stdlib.h>
int main() {
char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
return 0;
}
Bem, houve 5 bytes perdidos. Como isso aconteceu? O relatório de erro apenas diz
main
e malloc
. Em um programa maior, isso seria seriamente problemático de caçar. Isso ocorre devido à forma como o executável foi compilado . Na verdade, podemos obter detalhes linha por linha sobre o que deu errado. Recompile seu programa com um sinalizador de depuração (estou usando gcc
aqui):
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
Agora, com essa compilação de depuração, Valgrind aponta para a linha exata de código que aloca a memória que vazou! (A redação é importante: pode não ser exatamente onde está o vazamento, mas o que vazou. O rastreamento ajuda a encontrar onde .)
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 digitação.Às vezes, seus vazamentos / erros podem ser vinculados um ao outro, como um IDE descobrindo que você ainda não digitou um colchete. A resolução de um problema pode resolver outros, portanto, procure um que pareça um bom culpado e aplique algumas dessas idéias:
gdb
talvez) e procure por erros de pré-condição / pós-condição. A idéia é rastrear a execução do seu programa enquanto se concentra no tempo de vida da memória alocada.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)
E o 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 assistente de ensino, já vi esse erro com frequência. O aluno utiliza uma variável local e esquece de atualizar o ponteiro original. O erro aqui é perceber que realloc
realmente pode mover a memória alocada para outro lugar e alterar a localização do ponteiro. Em seguida, partimos resizeArray
sem dizer para
array->data
onde a matriz foi movida.
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)
E o 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;
}
Observe que Valgrind nos aponta a linha de código comentada acima. A matriz de tamanho 26 é indexada [0,25] e é por isso que *(alphabet + 26)
uma gravação inválida está fora dos limites. Uma gravação inválida é um resultado comum de erros de um por um. Olhe para o lado esquerdo da sua operação de atribuição.
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)
E o 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 aponta para a linha comentada acima. Veja a última iteração aqui, que é
*(destination + 26) = *(source + 26);
. No entanto, *(source + 26)
está fora dos limites novamente, da mesma forma que a gravação inválida. Leituras inválidas também são um resultado comum de erros off-by-one. Olhe para o lado direito da sua operação de atribuição.
Como sei quando o vazamento é meu? Como encontro meu vazamento quando estou usando o código de outra pessoa? Encontrei um vazamento que não é meu; devo fazer alguma coisa? Todos são perguntas legítimas. Primeiro, 2 exemplos do mundo real que mostram 2 classes de encontros comuns.
#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 é um programa simples: lê uma string JSON e a analisa. Ao fazer isso, usamos chamadas de biblioteca para fazer a análise para nós. Jansson faz as alocações necessárias dinamicamente, já que o JSON pode conter estruturas aninhadas. No entanto, isso não significa que nós decref
ou "liberamos" a memória que nos é fornecida de todas as funções. De fato, este código que escrevi acima gera uma "leitura inválida" e uma "gravação inválida". Esses erros desaparecem quando você retira a decref
linha para value
.
Por quê? A variável value
é considerada uma "referência emprestada" na API Jansson. A Jansson mantém o controle de sua memória para você, e você simplesmente precisa decref
estruturar o JSON independentemente uma da outra. A lição aqui:
leia a documentação . Realmente. Às vezes é difícil de entender, mas eles estão lhe dizendo por que essas coisas acontecem. Em vez disso, temos
perguntas existentes sobre esse erro de memória.
#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;
}
O que há de errado com este código ? Ele sempre vaza ~ 212 KiB de memória para mim. Tome um momento para pensar sobre isso. Ativamos e desativamos o SDL. Responda? Não há nada errado.
Isso pode parecer bizarro no começo . Verdade seja dita, os gráficos são confusos e às vezes você precisa aceitar alguns vazamentos como parte da biblioteca padrão. A lição aqui: você não precisa reprimir todos os vazamentos de memória . Às vezes, você só precisa suprimir os vazamentos, porque são problemas conhecidos dos quais você não pode fazer nada . (Esta não é minha permissão para ignorar seus próprios vazamentos!)
Como sei quando o vazamento é meu?
Isto é. (99% de certeza)
Como encontro meu vazamento quando estou usando o código de outra pessoa?
Provavelmente, alguém já o encontrou. Experimente o Google! Se isso falhar, use as habilidades que eu lhe dei acima. Se isso falhar e você vir principalmente chamadas de API e pouco de seu próprio rastreamento de pilha, consulte a próxima pergunta.
Encontrei um vazamento que não é meu; devo fazer alguma coisa?
Sim! A maioria das APIs tem maneiras de relatar bugs e problemas. Usa-os! Ajude a devolver as ferramentas que você está usando no seu projeto!
Obrigado por ficar comigo por tanto tempo. Espero que você tenha aprendido alguma coisa, pois tentei atender ao amplo espectro de pessoas que chegam a essa resposta. Espero que você tenha perguntado algumas coisas ao longo do caminho: Como o alocador de memória de C funciona? O que realmente é um vazamento de memória e um erro de memória? Como eles são diferentes de segfaults? Como funciona o Valgrind? Se você teve algum destes, alimente sua curiosidade:
memcheck
ferramenta está ativada por padrão?
memcheck
é a ferramenta padrão:--tool=<toolname> [default: memcheck]
Tente o seguinte:
valgrind --leak-check=full -v ./your_program
Enquanto o valgrind estiver instalado, ele percorrerá o seu programa e dirá o que há de errado. Pode fornecer dicas e locais aproximados onde seus vazamentos podem ser encontrados. Se você estiver fazendo uma segfault, tente executá-lo gdb
.
your_program
== o nome do executável ou qualquer comando que você usa para executar seu aplicativo.
Você pode correr:
valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
Você pode criar um alias no arquivo .bashrc da seguinte maneira
alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'
Portanto, sempre que quiser verificar vazamentos de memória, basta fazer
vg ./<name of your executable> <command line parameters to your executable>
Isso irá gerar um arquivo de log Valgrind no diretório atual.