Como uso o valgrind para encontrar vazamentos de memória?

181

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.

user484457
fonte
16
Você usa o valgrind para testar seu programa compilado , não o código fonte.
22411 Tony
6
A resposta dada abaixo por @RageD está correta. Por que você não a aceita?
Pratik Singhal
1
Um vazamento é causado por algo que você deixa de fazer - ou seja. liberte memória alocada. Portanto, o Valgrind não pode mostrar "onde" está o vazamento - apenas você sabe onde a memória alocada não é mais necessária. No entanto, informando qual alocação não está sendo livre () d, rastreando o uso dessa memória por meio do seu programa, você poderá determinar onde ela deve ficar livre () d. Um erro comum é sair de uma função sem liberar memória alocada.
MikeW
1
Relacionado: com qualquer ferramenta: stackoverflow.com/questions/6261201/…
Ciro Santilli escreveu

Respostas:

296

Como executar o Valgrind

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)

Eu tenho um vazamento, mas ONDE ?

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 maine 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 gccaqui):

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)

Técnicas para depurar vazamentos de memória e erros

  • Faça uso de www.cplusplus.com ! Possui excelente documentação sobre funções C / C ++.
  • Conselho geral para vazamentos de memória:
    • Verifique se a memória alocada dinamicamente é liberada.
    • Não aloque memória e esqueça de atribuir o ponteiro.
    • Não substitua um ponteiro por um novo, a menos que a memória antiga seja liberada.
  • Conselho geral para erros de memória:
    • Acesse e escreva para endereços e índices que certamente pertencem a você. Erros de memória são diferentes de vazamentos; geralmente são apenas IndexOutOfBoundsException problemas de digitação.
    • Não acesse ou grave na memória depois de liberá-la.
  • À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:

    • Liste as funções no seu código que dependem / são dependentes do código "ofensivo" que possui o erro de memória. Siga a execução do programa (talvez até gdbtalvez) 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.
    • Tente comentar o bloco de código "ofensivo" (dentro do razoável, para que seu código ainda seja compilado). Se o erro do Valgrind desaparecer, você encontrou onde está.
  • Se tudo mais falhar, tente procurar. Valgrind também tem documentação !

Um olhar sobre vazamentos e erros comuns

Assista seus ponteiros

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 reallocrealmente pode mover a memória alocada para outro lugar e alterar a localização do ponteiro. Em seguida, partimos resizeArraysem dizer para array->dataonde a matriz foi movida.

Gravação 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)

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.

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

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.


A topia de código aberto (U / Dys)

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.

Jansson : uma 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 é 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 decrefou "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 decreflinha 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.

SDL : uma biblioteca de gráficos e jogos

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

Respostas ao vazio

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!


Leitura adicional

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:

Joshua Detwiler
fonte
4
Resposta muito melhor, uma pena que esta não seja a resposta aceita.
A. Smoliak
Eu acredito que é uma boa prática fazer tal coisa, eu mesmo fiz algumas #
A. Smoliak 27/03/18
1
Posso marcar esta resposta com estrela e usá-la como referência futura para mim? Bom trabalho!
Zap
A memcheckferramenta está ativada por padrão?
abhiarora 03/04
@abhiarora Sim. A página do manual nos diz que memchecké a ferramenta padrão:--tool=<toolname> [default: memcheck]
Joshua Detwiler
146

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.

RageD
fonte
O que significa "seu_programa"? Esse local do código-fonte ou nome do aplicativo, como arquivo apk?
HoangVu
7
your_program== o nome do executável ou qualquer comando que você usa para executar seu aplicativo.
durou
27

Você pode correr:

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

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.

Sachin Rastogi
fonte