Alocação dinâmica: malloc e free
Nesta aula, você aprenderá sobre alocação dinâmica de memória em C usando as funções malloc e free, incluindo a importância de verificar o retorno NULL e como evitar vazamentos de memória. Através de exemplos práticos, você dominará a gestão manual de memória, essencial para programas eficientes e seguros.
A alocação dinâmica de memória é uma das características mais poderosas da linguagem C, permitindo que programas solicitem memória durante a execução conforme a necessidade. Diferentemente das variáveis estáticas ou automáticas, cujo tamanho é conhecido em tempo de compilação, a alocação dinâmica possibilita criar estruturas de dados flexíveis, como listas encadeadas, árvores e vetores de tamanho variável. As funções principais para gerenciar essa memória são malloc (memory allocation) e free (liberação). Nesta aula, abordaremos em detalhes como usar malloc com sizeof, a importância de free, a verificação de ponteiros nulos e como evitar vazamentos de memória.
malloc e sizeof
A função malloc aloca um bloco de memória contíguo de um tamanho especificado em bytes e retorna um ponteiro para o início desse bloco. O protótipo é void *malloc(size_t size);. O tipo size_t é um tipo inteiro sem sinal definido em <stddef.h> (ou <stdlib.h>). O valor retornado é um ponteiro genérico void*, que deve ser convertido para o tipo de ponteiro desejado. É fundamental usar sizeof para calcular o tamanho correto, garantindo portabilidade e evitando erros manuais.
Exemplo: alocar um vetor de 10 inteiros. sizeof(int) retorna o número de bytes que um int ocupa na plataforma (geralmente 4). Assim, malloc(10 * sizeof(int)) aloca espaço para 10 inteiros. O ponteiro retornado deve ser verificado contra NULL antes do uso.
#include <stdio.h>
#include <stdlib.h>
int main() {
int *vetor;
int n = 10;
vetor = (int *)malloc(n * sizeof(int));
if (vetor == NULL) {
fprintf(stderr, "Erro: memória insuficiente\n");
return 1;
}
// Uso do vetor
for (int i = 0; i < n; i++) {
vetor[i] = i * 2;
}
// Liberação
free(vetor);
return 0;
}
free
A função free libera a memória previamente alocada por malloc, calloc ou realloc. Seu protótipo é void free(void *ptr);. Após chamar free, o ponteiro se torna inválido (dangling pointer) e não deve ser dereferenciado. É boa prática atribuir NULL ao ponteiro após liberar para evitar uso acidental.
Chamar free duas vezes no mesmo ponteiro (double free) é um erro grave que pode corromper o gerenciador de memória. Também não se deve liberar memória que não foi alocada dinamicamente (como variáveis locais).
int *p = (int *)malloc(sizeof(int));
if (p != NULL) {
*p = 42;
printf("%d\n", *p);
free(p);
p = NULL; // evita dangling pointer
}
Verificando NULL
malloc retorna NULL quando não consegue alocar a memória solicitada (por exemplo, falta de espaço). Ignorar essa verificação pode levar a dereferenciamento de ponteiro nulo, causando falhas de segmentação. Portanto, sempre verifique se o retorno é NULL antes de usar o ponteiro.
Além disso, é importante notar que malloc(0) pode retornar NULL ou um ponteiro único que não deve ser dereferenciado. A norma permite ambos os comportamentos, então evite alocar zero bytes.
int *p = (int *)malloc(100 * sizeof(int));
if (p == NULL) {
// Tratamento de erro: encerrar ou tentar recuperação
exit(EXIT_FAILURE);
}
// ... uso ...
free(p);
Vazamentos de memória
Um vazamento de memória (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. Em programas longos ou sistemas embarcados, isso pode esgotar a memória disponível. Para evitar vazamentos, cada malloc deve ter um free correspondente, e é importante liberar toda a memória antes de o programa terminar.
Ferramentas como Valgrind (Linux) ou Dr. Memory (Windows) ajudam a detectar vazamentos. Boas práticas incluem: sempre parear alocação e liberação; usar ponteiros auxiliares para não perder a referência; e liberar estruturas complexas percorrendo todos os nós.
// Exemplo de vazamento: a memória alocada para 'buffer' nunca é liberada
void func() {
char *buffer = (char *)malloc(256);
// ... uso ...
// Esqueceu de free(buffer);
}
// Corrigido:
void func() {
char *buffer = (char *)malloc(256);
if (buffer != NULL) {
// ... uso ...
free(buffer);
}
}
Boas práticas
Sempre use sizeof com o tipo correto; evite números mágicos. Prefira p = malloc(sizeof(*p)) para alocar um único elemento, pois se o tipo de p mudar, a alocação se ajusta automaticamente. Nunca dereferencie um ponteiro após free. Em programas grandes, considere criar funções auxiliares para alocar e liberar memória de estruturas complexas, centralizando a lógica.
Referências
- cppreference: malloc
- cppreference: free
- GNU C Library: Memory Allocation
- Valgrind Quick Start
- POSIX: malloc
Exercícios
- Escreva um programa que aloque dinamicamente um vetor de 20 floats, preencha com valores de 1.0 a 20.0 e depois libere a memória. Inclua verificação de NULL.
- Crie uma função que aloque uma matriz de inteiros de dimensões m x n (passadas como parâmetros) e retorne o ponteiro para a matriz (use um único bloco de memória). A função deve retornar NULL em caso de falha.
- O que acontece se você chamar free duas vezes no mesmo ponteiro? Escreva um pequeno programa para demonstrar (não execute em produção).
- Explique por que é importante usar sizeof em malloc, em vez de um número fixo como 4 para int.
- Escreva um programa que aloque uma string de 100 caracteres, leia uma linha do usuário com fgets, e depois libere a memória. Inclua verificação de NULL.
#include <stdio.h>
#include <stdlib.h>
int main() {
float *vetor;
int n = 20;
vetor = (float *)malloc(n * sizeof(float));
if (vetor == NULL) {
fprintf(stderr, "Erro de alocação\n");
return 1;
}
for (int i = 0; i < n; i++) {
vetor[i] = (float)(i + 1);
}
free(vetor);
return 0;
}
#include <stdlib.h>
int *cria_matriz(int m, int n) {
int *matriz = (int *)malloc(m * n * sizeof(int));
return matriz; // pode ser NULL
}
Double free causa comportamento indefinido, geralmente corrompendo o heap e causando falha de segmentação. Exemplo:
#include <stdlib.h>
int main() {
int *p = malloc(sizeof(int));
free(p);
free(p); // undefined behavior
return 0;
}
O tamanho de tipos como int pode variar entre plataformas (ex.: 2 bytes em alguns sistemas embarcados, 4 bytes em PCs). Usar sizeof garante portabilidade e evita erros. Além disso, se o tipo mudar (ex.: de int para long), o sizeof se ajusta automaticamente, mantendo o código correto.
#include <stdio.h>
#include <stdlib.h>
int main() {
char *str = (char *)malloc(100 * sizeof(char));
if (str == NULL) {
fprintf(stderr, "Erro de alocação\n");
return 1;
}
printf("Digite uma linha: ");
if (fgets(str, 100, stdin) != NULL) {
printf("Você digitou: %s", str);
}
free(str);
return 0;
}