Alocação dinâmica: malloc e free
Nesta aula, você aprenderá a utilizar as funções malloc e free para alocar e liberar memória dinamicamente em C. Abordaremos o uso de sizeof, a verificação de ponteiros NULL e como evitar vazamentos de memória, com exemplos práticos e boas práticas.
Até agora, trabalhamos com variáveis cujo tamanho é conhecido em tempo de compilação (alocação estática). No entanto, muitas vezes precisamos alocar memória durante a execução do programa, por exemplo, para criar arrays cujo tamanho só é conhecido em tempo de execução ou para estruturas de dados dinâmicas como listas encadeadas. A alocação dinâmica de memória em C é feita principalmente com as funções malloc, calloc, realloc e free, todas definidas no cabeçalho <stdlib.h>.
Nesta aula, focaremos nas duas funções mais fundamentais: malloc para alocar memória e free para liberá-la. Entender corretamente seu uso é crucial para escrever programas eficientes e evitar problemas como vazamentos de memória e acesso inválido.
malloc e sizeof
A função malloc (memory allocation) aloca um bloco contíguo de memória de um determinado tamanho (em bytes) e retorna um ponteiro para o início desse bloco. Se a alocação falhar (por falta de memória), retorna NULL. A sintaxe é: void *malloc(size_t size);.
Para alocar espaço para um tipo específico, usamos o operador sizeof para obter o tamanho em bytes daquele tipo. Isso torna o código portável e evita erros de tamanho. Por exemplo, para alocar um array de 10 inteiros: int *p = (int *) malloc(10 * sizeof(int));. O cast (int *) não é obrigatório em C, mas é comum para clareza.
Exemplo prático:
#include <stdio.h>
#include <stdlib.h>
int main() {
int n;
printf("Quantos inteiros deseja alocar? ");
scanf("%d", &n);
int *arr = (int *) malloc(n * sizeof(int));
if (arr == NULL) {
printf("Erro de alocação!\n");
return 1;
}
for (int i = 0; i < n; i++) {
arr[i] = i * 10;
}
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}Note que malloc não inicializa a memória alocada; o conteúdo é indeterminado. Se precisar de memória zerada, use calloc.
free
A função free libera a memória previamente alocada por malloc, calloc ou realloc. Sua sintaxe é: void free(void *ptr);. Após chamar free, o ponteiro se torna inválido (dangling pointer) e não deve ser usado. É uma boa prática atribuir NULL ao ponteiro após liberá-lo.
Chamar free duas vezes no mesmo ponteiro (double free) ou em um ponteiro não alocado causa comportamento indefinido. Sempre libere a memória alocada quando não for mais necessária para evitar vazamentos.
Exemplo:
int *p = (int *) malloc(sizeof(int));
if (p != NULL) {
*p = 42;
printf("%d\n", *p);
free(p);
p = NULL; // evita dangling pointer
}Verificando NULL
Sempre verifique se malloc retornou NULL antes de usar o ponteiro. Isso indica que a alocação falhou. Ignorar essa verificação pode levar a falhas de segmentação. A verificação é simples: if (ptr == NULL) { /* trata erro */ }.
Exemplo de verificação:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int *) malloc(1000000000 * sizeof(int)); // alocação grande
if (arr == NULL) {
printf("Memória insuficiente!\n");
return 1;
}
// ... uso ...
free(arr);
return 0;
}Em programas robustos, é essencial tratar o erro de alocação adequadamente, seja exibindo uma mensagem, liberando recursos e encerrando, ou tentando uma alocação menor.
Vazamentos de memória
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 de longa execução (servidores, sistemas embarcados), vazamentos podem levar à exaustão de memória e falhas.
Para evitar vazamentos, cada malloc deve ter um free correspondente. Use ferramentas como Valgrind (Linux) ou Dr. Memory (Windows) para detectar vazamentos. Além disso, adote boas práticas:
- Libere memória assim que não for mais necessária.
- Atribua NULL ao ponteiro após free.
- Evite perder o endereço do bloco alocado antes de liberá-lo.
- Em funções que retornam ponteiros alocados, documente que o chamador é responsável por liberar.
Exemplo de vazamento:
void func() {
int *p = (int *) malloc(sizeof(int));
*p = 10;
// esqueceu de free(p);
}
int main() {
func();
// p nunca foi liberado -> vazamento
return 0;
}Para corrigir, adicione free(p); antes do fim da função.
Boas práticas finais
- Sempre inclua
<stdlib.h>para usar malloc e free. - Use sizeof para calcular o tamanho, não valores fixos.
- Verifique o retorno de malloc para NULL.
- Libere a memória alocada e evite double free.
- Para arrays, aloque o tamanho total (número de elementos * sizeof(tipo)).
Referências
- cppreference: malloc
- cppreference: free
- GNU C Library: Memory Allocation
- Valgrind Quick Start
- Wikipedia: Vazamento de memória
Exercícios
Escreva um programa que aloca dinamicamente um array de 5 floats, preenche com valores lidos do usuário e imprime a soma.
✓ Resposta:#include <stdio.h> #include <stdlib.h> int main() { int n = 5; float *arr = (float *) malloc(n * sizeof(float)); if (arr == NULL) { printf("Erro de alocação\n"); return 1; } printf("Digite 5 números: "); for (int i = 0; i < n; i++) { scanf("%f", &arr[i]); } float soma = 0; for (int i = 0; i < n; i++) { soma += arr[i]; } printf("Soma: %.2f\n", soma); free(arr); return 0; }O que acontece se você chamar free duas vezes no mesmo ponteiro? Faça um programa que demonstre (mas não execute em produção).
✓ Resposta: Chamar free duas vezes no mesmo ponteiro causa comportamento indefinido (double free). Pode corromper o heap, causar crash ou ser explorado por atacantes. Exemplo:int *p = malloc(sizeof(int)); free(p); free(p); // undefined behaviorEscreva uma função que recebe um inteiro n, aloca um array de n inteiros, preenche com zeros e retorna o ponteiro. O chamador deve liberar a memória.
✓ Resposta:#include <stdlib.h> int *cria_array_zeros(int n) { int *arr = (int *) malloc(n * sizeof(int)); if (arr != NULL) { for (int i = 0; i < n; i++) { arr[i] = 0; } } return arr; } // Exemplo de uso: int main() { int *meu_arr = cria_array_zeros(10); if (meu_arr) { // usar... free(meu_arr); } return 0; }Identifique o vazamento de memória no código abaixo e corrija:
void exemplo() { int *p = malloc(sizeof(int)); *p = 42; int *q = malloc(sizeof(int)); *q = 24; p = q; free(p); }✓ Resposta: O vazamento ocorre porque o bloco apontado por p originalmente (com valor 42) não é liberado antes de p ser sobrescrito. Correção:void exemplo() { int *p = malloc(sizeof(int)); *p = 42; int *q = malloc(sizeof(int)); *q = 24; free(p); // libera o primeiro bloco p = q; free(p); // libera o segundo }Escreva um programa que aloca uma matriz dinâmica de inteiros com linhas e colunas lidas do usuário, preenche com valores (linha * coluna) e imprime. Não esqueça de liberar a memória.
✓ Resposta:#include <stdio.h> #include <stdlib.h> int main() { int linhas, colunas; printf("Linhas e colunas: "); scanf("%d %d", &linhas, &colunas); int **mat = (int **) malloc(linhas * sizeof(int *)); if (mat == NULL) return 1; for (int i = 0; i < linhas; i++) { mat[i] = (int *) malloc(colunas * sizeof(int)); if (mat[i] == NULL) { // libera linhas já alocadas for (int j = 0; j < i; j++) free(mat[j]); free(mat); return 1; } } for (int i = 0; i < linhas; i++) { for (int j = 0; j < colunas; j++) { mat[i][j] = i * colunas + j; } } for (int i = 0; i < linhas; i++) { for (int j = 0; j < colunas; j++) { printf("%d ", mat[i][j]); } printf("\n"); } for (int i = 0; i < linhas; i++) free(mat[i]); free(mat); return 0; }