Vazamentos e erros de memória
Esta aula aborda os principais erros de gerenciamento de memória em C: memory leaks, double free e use-after-free, com exemplos práticos e explicações detalhadas. Também é apresentada uma visão geral do Valgrind, ferramenta essencial para detectar esses problemas. Ao final, o aluno terá exercícios para consolidar o aprendizado.
Gerenciar memória manualmente em C é uma das maiores responsabilidades do programador. Erros como vazamentos, liberações duplicadas e acesso após liberação podem causar falhas, comportamento indefinido e vulnerabilidades de segurança. Nesta aula, vamos explorar cada um desses problemas, suas causas e consequências, e aprender a usar o Valgrind para identificá-los.
Dominar esses conceitos é crucial para escrever código robusto e eficiente. Vamos começar com o memory leak, o mais comum dos erros.
Memory leaks
Um memory leak ocorre quando a memória alocada dinamicamente não é liberada após o uso, fazendo com que o programa consuma cada vez mais memória. Com o tempo, isso pode levar à exaustão de memória e queda do programa. Em C, isso acontece quando o ponteiro que aponta para a memória alocada é perdido ou sobrescrito sem chamar free().
Exemplo clássico: alocar memória e retornar sem liberar, ou alocar em um loop sem liberar entre as iterações.
#include <stdlib.h>
void func() {
int *p = malloc(sizeof(int) * 100);
// esqueceu de free(p);
}
int main() {
func();
// p não existe mais, mas a memória continua alocada
return 0;
}
Para evitar leaks, sempre pareie cada malloc/calloc/realloc com um free correspondente. Use ferramentas como Valgrind para detectar leaks.
Double free
Double free ocorre quando a função free() é chamada duas vezes para o mesmo bloco de memória. Isso corrompe as estruturas internas do gerenciador de memória, podendo causar falhas ou comportamento imprevisível. O C não protege contra isso; é responsabilidade do programador.
Exemplo:
#include <stdlib.h>
int main() {
int *p = malloc(sizeof(int));
free(p);
free(p); // ERRO: double free
return 0;
}
Para evitar, após liberar um ponteiro, atribua NULL a ele. Assim, chamar free(NULL) é seguro (não faz nada).
free(p);
p = NULL;
// se free(p) for chamado novamente, é seguro
Use-after-free
Use-after-free acontece quando acessamos um ponteiro que já foi liberado. O endereço pode ainda conter dados, mas eles podem ser sobrescritos ou realocados. Isso é um comportamento indefinido e pode ser explorado por atacantes.
Exemplo:
#include <stdlib.h>
#include <stdio.h>
int main() {
int *p = malloc(sizeof(int));
*p = 42;
free(p);
printf("%d\n", *p); // ERRO: use-after-free
return 0;
}
Para evitar, nunca use um ponteiro após liberá-lo. Atribuir NULL após free ajuda a detectar o erro, pois acessar NULL causa uma falha imediata (segmentation fault), que é mais fácil de depurar.
Valgrind (visão geral)
Valgrind é uma ferramenta de instrumentação que ajuda a detectar erros de memória. Ele executa o programa em um ambiente controlado e monitora todas as alocações e liberações. O Valgrind pode detectar memory leaks, double free, use-after-free, acesso a memória não inicializada, entre outros.
Para usar, compile o programa com informações de depuração (-g) e execute:
gcc -g -o meu_prog meu_prog.c
valgrind --leak-check=full ./meu_prog
A saída mostrará erros como:
==12345== Invalid write of size 4
==12345== at 0x...: main (exemplo.c:5)
==12345== Address 0x... is 0 bytes inside a block of size 4 free'd
==12345== by 0x...: free (exemplo.c:7)
Para memory leaks, Valgrind exibe um resumo de blocos alocados e não liberados. É uma ferramenta indispensável no desenvolvimento de programas em C.
Boas práticas e observações finais
Sempre inicialize ponteiros com NULL, libere memória no mesmo nível de abstração em que foi alocada, e use ferramentas automáticas como Valgrind. Em projetos grandes, considere usar um padrão de alocação consistente e revisões de código focadas em gerenciamento de memória.
Referências
- cppreference: malloc
- cppreference: free
- Valgrind Quick Start
- Valgrind Memcheck Manual
- GNU C Library: Freeing after Malloc
- Stack Overflow: Use-after-free example
Exercícios
Escreva um programa que aloca memória para um array de 10 inteiros, preenche com valores de 0 a 9 e depois libera a memória. Em seguida, tente imprimir o primeiro elemento. O que acontece? Corrija o código.
✓ Resposta: O código original causa use-after-free. A correção é não acessar o ponteiro após free. Exemplo corrigido:#include <stdio.h> #include <stdlib.h> int main() { int *arr = malloc(10 * sizeof(int)); if (arr == NULL) return 1; for (int i = 0; i < 10; i++) arr[i] = i; free(arr); // arr = NULL; // boa prática // não acessar arr agora return 0; }O que é um memory leak? Dê um exemplo e explique como Valgrind pode detectá-lo.
✓ Resposta: Memory leak é quando memória alocada não é liberada. Exemplo:
Valgrind com --leak-check=full mostra blocos definitivamente perdidos.void leak() { int *p = malloc(100); // sem free }Explique o que é double free e como evitá-lo.
✓ Resposta: Double free é chamar free duas vezes no mesmo ponteiro. Evita-se atribuindo NULL ao ponteiro após free, pois free(NULL) é seguro.Considere o código:
int *p = malloc(sizeof(int)); free(p); *p = 10;Que tipo de erro é esse? Qual a consequência?✓ Resposta: Use-after-free. A consequência é comportamento indefinido: pode corromper dados, causar falha ou ser explorado.Como você usaria Valgrind para verificar um programa? Escreva o comando e explique o que a opção --leak-check=full faz.
✓ Resposta: Comando:valgrind --leak-check=full ./programa. A opção --leak-check=full ativa a detecção detalhada de memory leaks, mostrando exatamente onde a memória foi alocada e não liberada.