Alocação dinâmica: malloc, calloc, realloc e free
1. Introdução à alocação dinâmica de memória
1.1. Diferença entre alocação estática, automática e dinâmica
Em C, a memória pode ser gerenciada de três formas principais:
- Estática: variáveis globais e
staticexistem durante toda a execução do programa. - Automática: variáveis locais (na pilha) são criadas ao entrar em um escopo e destruídas ao sair.
- Dinâmica: blocos de memória no heap são alocados e liberados manualmente pelo programador.
A alocação dinâmica oferece flexibilidade para criar estruturas de tamanho variável em tempo de execução.
1.2. Quando e por que usar alocação dinâmica em C
Use alocação dinâmica quando:
- O tamanho dos dados não é conhecido em tempo de compilação.
- Você precisa de estruturas que crescem ou encolhem (listas, árvores).
- O escopo da memória precisa ultrapassar a função que a criou.
- Os dados são grandes demais para caber na pilha.
1.3. Visão geral das funções da biblioteca <stdlib.h>
As quatro funções fundamentais para gerenciamento de heap em C são:
#include <stdlib.h>
void *malloc(size_t size);
void *calloc(size_t num, size_t size);
void *realloc(void *ptr, size_t new_size);
void free(void *ptr);
2. malloc: alocação de memória bruta
2.1. Sintaxe e funcionamento
malloc aloca um bloco contíguo de size bytes e retorna um ponteiro void* para o início do bloco. O conteúdo não é inicializado.
int *p = (int*) malloc(10 * sizeof(int));
if (p == NULL) {
fprintf(stderr, "Falha na alocação de memória\n");
exit(1);
}
2.2. Conversão de ponteiro void* para o tipo desejado
Em C padrão (não C++), o cast é opcional, mas recomendado para clareza:
double *vetor = malloc(5 * sizeof(double)); // sem cast
char *buffer = (char*) malloc(256 * sizeof(char)); // com cast
2.3. Verificação de falha na alocação
Sempre verifique se o retorno é NULL. Um malloc pode falhar por falta de memória disponível.
int *dados = malloc(1000000 * sizeof(int));
if (!dados) {
perror("malloc");
return -1;
}
3. calloc: alocação com inicialização zero
3.1. Sintaxe
calloc aloca memória para num elementos de size bytes cada e inicializa todos os bits com zero.
int *arr = (int*) calloc(20, sizeof(int));
// Equivalente a malloc(20 * sizeof(int)) + memset(arr, 0, 20 * sizeof(int))
3.2. Diferenças fundamentais entre malloc e calloc
| Característica | malloc |
calloc |
|---|---|---|
| Parâmetros | 1 (tamanho total) | 2 (quantidade e tamanho) |
| Inicialização | Não (lixo de memória) | Sim (tudo zero) |
| Performance | Mais rápido | Mais lento (inicializa) |
| Segurança | Menor | Maior |
3.3. Vantagens de segurança
Usar calloc evita bugs causados por acesso a memória não inicializada:
struct Pessoa *pessoas = calloc(10, sizeof(struct Pessoa));
// Todos os campos numéricos serão 0, ponteiros serão NULL
4. realloc: redimensionamento de blocos alocados
4.1. Sintaxe
realloc redimensiona um bloco previamente alocado para new_size bytes.
int *arr = malloc(5 * sizeof(int));
arr = realloc(arr, 10 * sizeof(int)); // Expande para 10 elementos
4.2. Comportamento
- Se o novo tamanho for maior, o conteúdo original é preservado e os novos bytes são não inicializados.
- Se for menor, o conteúdo é truncado.
- Se não for possível expandir no mesmo lugar,
reallocaloca nova região, copia os dados e libera a antiga.
int *temp = realloc(arr, 20 * sizeof(int));
if (temp == NULL) {
// arr ainda é válido, mas não foi redimensionado
free(arr);
exit(1);
}
arr = temp;
4.3. Cuidados importantes
Nunca faça arr = realloc(arr, novo_tamanho) diretamente sem variável temporária. Se realloc falhar, você perde o ponteiro original.
5. free: liberação de memória alocada
5.1. Sintaxe e regras
free libera o bloco apontado por ptr para o heap.
int *p = malloc(100 * sizeof(int));
// ... usa p ...
free(p);
Regras fundamentais:
- Só libere memória que foi alocada com malloc, calloc ou realloc.
- Nunca libere o mesmo ponteiro duas vezes.
- Liberar NULL é seguro (não faz nada).
5.2. Consequências de não liberar
Memory leak: a memória nunca é devolvida ao sistema, causando degradação e eventual falha.
// Exemplo de vazamento em loop
for (int i = 0; i < 1000; i++) {
char *buf = malloc(1024); // sem free!
}
5.3. Boas práticas: ponteiros nulos após free
Atribua NULL ao ponteiro após liberá-lo para evitar ponteiros soltos (dangling pointers):
free(ptr);
ptr = NULL;
6. Armadilhas comuns e erros frequentes
6.1. Acesso a memória já liberada (dangling pointers)
int *p = malloc(sizeof(int));
free(p);
*p = 42; // Comportamento indefinido!
6.2. Vazamentos em funções
char* criar_string() {
char *s = malloc(100);
strcpy(s, "Olá");
return s; // Quem chamou deve liberar!
}
int main() {
char *str = criar_string();
// ... esqueceu de free(str) ...
return 0;
}
6.3. Subalocação e estouro de buffer
int *arr = malloc(5 * sizeof(int));
arr[10] = 42; // Estouro de buffer! Acesso fora da área alocada
7. Exemplos práticos integrados
7.1. Vetor dinâmico com malloc e realloc
#include <stdio.h>
#include <stdlib.h>
int main() {
int capacidade = 4;
int *vetor = malloc(capacidade * sizeof(int));
int tamanho = 0;
for (int i = 0; i < 10; i++) {
if (tamanho >= capacidade) {
capacidade *= 2;
int *temp = realloc(vetor, capacidade * sizeof(int));
if (temp == NULL) {
free(vetor);
return 1;
}
vetor = temp;
}
vetor[tamanho++] = i * i;
}
for (int i = 0; i < tamanho; i++) {
printf("%d ", vetor[i]);
}
printf("\n");
free(vetor);
return 0;
}
7.2. Matriz alocada dinamicamente com calloc
#include <stdio.h>
#include <stdlib.h>
int main() {
int linhas = 3, colunas = 4;
int **matriz = calloc(linhas, sizeof(int*));
for (int i = 0; i < linhas; i++) {
matriz[i] = calloc(colunas, sizeof(int));
}
// Preenche e exibe a matriz
for (int i = 0; i < linhas; i++) {
for (int j = 0; j < colunas; j++) {
matriz[i][j] = i * colunas + j;
printf("%2d ", matriz[i][j]);
}
printf("\n");
}
// Liberação: ordem inversa da alocação
for (int i = 0; i < linhas; i++) {
free(matriz[i]);
}
free(matriz);
return 0;
}
7.3. Uso combinado com structs
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *nome;
int idade;
} Pessoa;
Pessoa* criar_pessoa(const char *nome, int idade) {
Pessoa *p = malloc(sizeof(Pessoa));
p->nome = malloc(strlen(nome) + 1);
strcpy(p->nome, nome);
p->idade = idade;
return p;
}
void destruir_pessoa(Pessoa *p) {
free(p->nome);
free(p);
}
int main() {
Pessoa *joao = criar_pessoa("João", 30);
printf("%s tem %d anos\n", joao->nome, joao->idade);
destruir_pessoa(joao);
return 0;
}
8. Boas práticas e ferramentas de depuração
8.1. Estratégias para evitar memory leaks
- Sempre pareie cada
malloc/calloc/realloccom umfree. - Use contadores de alocações em depuração.
- Em projetos grandes, considere wrappers que rastreiam alocações.
8.2. Introdução ao Valgrind
Valgrind é uma ferramenta essencial para detectar vazamentos e erros de memória:
$ gcc -g programa.c -o programa
$ valgrind --leak-check=full ./programa
8.3. Padrões de design
- Alocar e liberar no mesmo escopo: quando possível, mantenha alocação e liberação na mesma função.
- Documente a responsabilidade: comente quem deve liberar a memória.
- Use funções de criação/destruição: encapsule alocação e liberação em funções pareadas.
Referências
- cppreference.com - malloc — Documentação completa da função malloc com exemplos e comportamento detalhado.
- cppreference.com - calloc — Referência oficial da função calloc, incluindo diferenças de inicialização.
- cppreference.com - realloc — Detalhes sobre redimensionamento de blocos e tratamento de falhas.
- cppreference.com - free — Regras e boas práticas para liberação de memória.
- Valgrind User Manual — Guia oficial da ferramenta Valgrind para detecção de vazamentos e erros de memória.
- GeeksforGeeks - Dynamic Memory Allocation in C — Tutorial prático com exemplos detalhados de todas as funções.
- IBM Documentation - Memory Allocation — Documentação técnica da IBM sobre alocação dinâmica em C.