Arrays dinâmicos
Nesta aula, você aprenderá a criar e gerenciar arrays dinâmicos em C utilizando alocação na heap, redimensionamento com realloc, matrizes dinâmicas e liberação de memória. O conteúdo aborda desde a alocação com malloc até boas práticas para evitar vazamentos de memória.
Arrays dinâmicos são estruturas de dados fundamentais em C que permitem alocar memória em tempo de execução, ajustando-se às necessidades do programa. Diferentemente dos arrays estáticos, cujo tamanho é fixo em tempo de compilação, os arrays dinâmicos residem na heap e podem ser redimensionados durante a execução. Isso oferece flexibilidade para lidar com quantidades de dados que só são conhecidas em tempo de execução, como listas de entrada do usuário ou dados lidos de arquivos.
Nesta aula, exploraremos as funções da biblioteca padrão stdlib.h — malloc, calloc, realloc e free — para gerenciar arrays dinâmicos. Veremos como alocar, redimensionar, criar matrizes dinâmicas e, crucialmente, liberar a memória alocada para evitar vazamentos. Ao final, você terá uma compreensão sólida de como trabalhar com memória dinâmica em C.
Alocando arrays na heap
Para alocar um array dinamicamente, usamos a função malloc (memory allocation), que recebe o número de bytes a serem alocados e retorna um ponteiro para o início do bloco de memória. O tipo de retorno é void*, então é necessário fazer um cast para o tipo desejado. A função calloc é similar, mas inicializa todos os bytes com zero e recebe dois argumentos: o número de elementos e o tamanho de cada elemento.
Exemplo: alocar um array de 10 inteiros:
#include <stdlib.h>
int *arr = (int*) malloc(10 * sizeof(int));
if (arr == NULL) {
// Tratar erro de alocação
}É essencial verificar se o ponteiro retornado é NULL, o que indica falha na alocação. Após a alocação, podemos acessar os elementos normalmente com a sintaxe de array: arr[0], arr[1], etc.
Redimensionando
Às vezes, precisamos aumentar ou diminuir o tamanho de um array dinâmico. A função realloc (reallocation) permite alterar o tamanho de um bloco de memória previamente alocado. Ela recebe o ponteiro original e o novo tamanho em bytes, e retorna um ponteiro para o novo bloco (que pode ser o mesmo ou outro endereço). O conteúdo antigo é preservado até o mínimo entre o tamanho antigo e o novo.
Exemplo: redimensionar o array para 20 inteiros:
arr = (int*) realloc(arr, 20 * sizeof(int));
if (arr == NULL) {
// Tratar erro
}Se realloc falhar, ela retorna NULL e o bloco original permanece intacto. Portanto, é recomendável usar um ponteiro temporário para evitar perder a referência original:
int *temp = (int*) realloc(arr, 20 * sizeof(int));
if (temp != NULL) {
arr = temp;
} else {
// arr ainda aponta para o bloco original
}Quando o novo tamanho é menor, os elementos excedentes são descartados. Quando é maior, os novos elementos não são inicializados (a menos que usemos calloc ou inicializemos manualmente).
Matrizes dinâmicas
Matrizes dinâmicas (arrays bidimensionais) podem ser alocadas de duas formas principais: como um único bloco contíguo (alocação plana) ou como um array de ponteiros (alocação fragmentada). A forma mais comum e eficiente é a alocação plana, onde alocamos um único bloco de linhas * colunas * sizeof(tipo) e acessamos os elementos com matriz[i * colunas + j].
Exemplo: matriz 3x4 de inteiros:
int linhas = 3, colunas = 4;
int *matriz = (int*) malloc(linhas * colunas * sizeof(int));
if (matriz == NULL) { /* erro */ }
// Acesso ao elemento [i][j]:
matriz[i * colunas + j] = valor;A segunda forma é alocar um array de ponteiros, onde cada ponteiro aponta para uma linha alocada separadamente:
int **matriz = (int**) malloc(linhas * sizeof(int*));
for (int i = 0; i < linhas; i++) {
matriz[i] = (int*) malloc(colunas * sizeof(int));
}Essa abordagem permite acesso com sintaxe matriz[i][j], mas tem overhead de alocações múltiplas e pior localidade de cache. A liberação também exige um loop para cada linha.
Liberando
Toda memória alocada dinamicamente deve ser liberada com a função free quando não for mais necessária. Caso contrário, ocorre vazamento de memória, que pode esgotar os recursos do sistema. A função free recebe o ponteiro retornado por malloc, calloc ou realloc e libera o bloco.
Exemplo:
free(arr);
arr = NULL; // boa prática para evitar ponteiros soltosPara matrizes alocadas como array de ponteiros, devemos liberar cada linha primeiro e depois o array de ponteiros:
for (int i = 0; i < linhas; i++) {
free(matriz[i]);
}
free(matriz);
matriz = NULL;Nunca tente liberar o mesmo bloco duas vezes (double free), pois isso causa comportamento indefinido. Sempre atribua NULL ao ponteiro após liberar, especialmente se o ponteiro puder ser usado novamente.
Boas práticas
1. Sempre verifique o retorno das funções de alocação. 2. Use sizeof para calcular o tamanho correto. 3. Prefira alocação contígua para matrizes por desempenho. 4. Libere a memória na ordem inversa da alocação. 5. Evite esconder a alocação dentro de funções sem documentar a responsabilidade de liberação.
Referências
- malloc - cppreference.com
- calloc - cppreference.com
- realloc - cppreference.com
- free - cppreference.com
- GNU C Library: Memory Allocation
- IBM: malloc() - Reserve storage block
- Alocação dinâmica de memória - Wikipedia
Exercícios
Escreva um programa que aloque dinamicamente um array de 5 inteiros, preencha com valores de 1 a 5 e imprima. Depois, redimensione para 10 inteiros, preencha os novos elementos com 0 e imprima novamente.
✓ Resposta:#include <stdio.h> #include <stdlib.h> int main() { int *arr = (int*) malloc(5 * sizeof(int)); if (arr == NULL) return 1; for (int i = 0; i < 5; i++) arr[i] = i + 1; for (int i = 0; i < 5; i++) printf("%d ", arr[i]); printf("\n"); int *temp = (int*) realloc(arr, 10 * sizeof(int)); if (temp == NULL) { free(arr); return 1; } arr = temp; for (int i = 5; i < 10; i++) arr[i] = 0; for (int i = 0; i < 10; i++) printf("%d ", arr[i]); printf("\n"); free(arr); return 0; }Crie uma função que receba um ponteiro para um array de inteiros e seu tamanho, e redimensione o array para o dobro do tamanho, mantendo os valores antigos e inicializando os novos com -1. A função deve modificar o ponteiro original.
✓ Resposta:void dobrar(int **arr, int *tamanho) { int novoTamanho = (*tamanho) * 2; int *temp = (int*) realloc(*arr, novoTamanho * sizeof(int)); if (temp == NULL) return; *arr = temp; for (int i = *tamanho; i < novoTamanho; i++) { (*arr)[i] = -1; } *tamanho = novoTamanho; }Implemente uma matriz dinâmica 4x5 usando alocação contígua. Preencha com valores i*j e imprima. Depois, libere a memória.
✓ Resposta:#include <stdio.h> #include <stdlib.h> int main() { int linhas = 4, colunas = 5; int *mat = (int*) malloc(linhas * colunas * sizeof(int)); if (mat == NULL) return 1; for (int i = 0; i < linhas; i++) { for (int j = 0; j < colunas; j++) { mat[i * colunas + j] = i * j; } } for (int i = 0; i < linhas; i++) { for (int j = 0; j < colunas; j++) { printf("%3d ", mat[i * colunas + j]); } printf("\n"); } free(mat); return 0; }Escreva um programa que leia um número n do usuário, aloque um array de n floats, leia n valores, calcule a média e imprima. Não esqueça de liberar a memória.
✓ Resposta:#include <stdio.h> #include <stdlib.h> int main() { int n; printf("Digite n: "); scanf("%d", &n); float *arr = (float*) malloc(n * sizeof(float)); if (arr == NULL) return 1; float soma = 0; for (int i = 0; i < n; i++) { scanf("%f", &arr[i]); soma += arr[i]; } printf("Média: %.2f\n", soma / n); free(arr); return 0; }Corrija o código a seguir que contém um vazamento de memória e um possível double free. Explique os problemas.
int *p = malloc(10 * sizeof(int));
p = malloc(20 * sizeof(int));
free(p);
free(p);✓ Resposta:O primeiro
mallocaloca 10 inteiros e o ponteiro é armazenado em p. Em seguida, um segundomallocaloca 20 inteiros e atribui a p, sobrescrevendo o endereço anterior. O bloco de 10 inteiros nunca é liberado, causando vazamento de memória. Depois,free(p)libera o bloco de 20 inteiros corretamente. O segundofree(p)tenta liberar o mesmo bloco novamente (double free), o que é comportamento indefinido. Correção: liberar o primeiro bloco antes de sobrescrever p, e evitar o segundo free.int *p = malloc(10 * sizeof(int)); free(p); p = malloc(20 * sizeof(int)); // usar p... free(p); p = NULL;