calloc e realloc
Esta aula aborda as funções calloc e realloc da biblioteca padrão de C, explicando como alocar memória zerada e redimensionar blocos de memória dinâmica. Inclui exemplos práticos, cuidados importantes e exercícios para fixação.
Nesta aula, vamos explorar duas funções essenciais para alocação dinâmica de memória em C: calloc e realloc. Enquanto malloc aloca memória sem inicializar, calloc aloca e zera a memória. Já realloc permite redimensionar um bloco alocado anteriormente, preservando seu conteúdo. Compreender essas funções é crucial para gerenciar memória de forma eficiente e segura.
Vamos ver como usá-las corretamente, quais os cuidados necessários e como evitar erros comuns, como vazamentos de memória e acesso a áreas inválidas.
calloc (zerado)
A função calloc (contiguous allocation) aloca memória para um array de elementos, cada um de tamanho especificado, e inicializa todos os bytes com zero. Sua assinatura é:
void *calloc(size_t num, size_t size);
Ela recebe dois argumentos: o número de elementos (num) e o tamanho de cada elemento (size). Retorna um ponteiro para o início do bloco alocado, ou NULL se a alocação falhar. A memória alocada é garantidamente zerada, o que é útil para estruturas que precisam de valores iniciais nulos, como arrays de contadores ou strings vazias.
Exemplo de uso:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr;
int n = 5;
arr = (int*) calloc(n, sizeof(int));
if (arr == NULL) {
printf("Falha na alocação\n");
return 1;
}
// Todos os elementos são 0
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
free(arr);
return 0;
}
Neste exemplo, arr[0] a arr[4] são inicializados com 0. Note que calloc é ligeiramente mais lenta que malloc devido à inicialização, mas oferece segurança extra.
realloc (redimensionar)
A função realloc (reallocation) redimensiona um bloco de memória previamente alocado por malloc, calloc ou realloc. Sua assinatura é:
void *realloc(void *ptr, size_t new_size);
Ela recebe um ponteiro para o bloco original (ptr) e o novo tamanho em bytes (new_size). Retorna um ponteiro para o bloco redimensionado (que pode ser o mesmo ou outro endereço), ou NULL se a realocação falhar. Se ptr for NULL, realloc se comporta como malloc. Se new_size for zero, o comportamento é definido pela implementação (geralmente libera a memória e retorna NULL).
Exemplo de redimensionamento:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int*) malloc(3 * sizeof(int));
if (arr == NULL) return 1;
arr[0] = 10; arr[1] = 20; arr[2] = 30;
// Redimensionar para 5 elementos
int *temp = (int*) realloc(arr, 5 * sizeof(int));
if (temp == NULL) {
free(arr);
return 1;
}
arr = temp;
arr[3] = 40; arr[4] = 50;
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
free(arr);
return 0;
}
Após realloc, os três primeiros elementos preservam seus valores. Os novos elementos (índices 3 e 4) têm valores indeterminados, pois realloc não inicializa a nova memória.
Cuidados com realloc
realloc é poderosa, mas requer atenção para evitar erros comuns:
- Não use o ponteiro original após realocação: Se
reallocmover o bloco, o ponteiro original se torna inválido. Sempre atribua o retorno a um ponteiro temporário e verifique se éNULLantes de sobrescrever o original. - Vazamento de memória se a realocação falhar: Se
reallocretornarNULL, o bloco original permanece intacto. Se você sobrescrever o ponteiro original diretamente, perderá a referência para liberá-lo depois. Use um ponteiro temporário. - Comportamento com tamanho zero: Evite usar
realloccomnew_size = 0, pois pode liberar a memória ou retornarNULL. Prefirafreeexplicitamente. - Alinhamento e fragmentação: Muitas realocações podem fragmentar a memória. Considere estratégias como alocar blocos maiores e gerenciar manualmente.
Exemplo de uso seguro:
int *arr = malloc(10 * sizeof(int));
if (!arr) { /* erro */ }
// ...
int *new_arr = realloc(arr, 20 * sizeof(int));
if (new_arr == NULL) {
// arr ainda é válido, podemos continuar usando ou liberar
free(arr);
exit(1);
}
arr = new_arr; // agora arr aponta para o bloco redimensionado
Exemplos
Vamos ver um exemplo completo que combina calloc e realloc para gerenciar um array dinâmico de números lidos do usuário:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *numbers = NULL;
int capacity = 0;
int count = 0;
int input;
printf("Digite números (0 para sair):\n");
while (1) {
scanf("%d", &input);
if (input == 0) break;
if (count == capacity) {
capacity = capacity == 0 ? 4 : capacity * 2;
int *temp = realloc(numbers, capacity * sizeof(int));
if (temp == NULL) {
printf("Erro de alocação\n");
free(numbers);
return 1;
}
numbers = temp;
}
numbers[count++] = input;
}
printf("Números digitados: ");
for (int i = 0; i < count; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
free(numbers);
return 0;
}
Neste exemplo, usamos realloc para expandir o array conforme necessário. Inicialmente, numbers é NULL, e a primeira chamada a realloc age como malloc. Note que usamos um ponteiro temporário temp para evitar vazamento.
Boas Práticas
- Sempre verifique o retorno de
callocerealloc. - Use
callocquando precisar de memória zerada, especialmente para estruturas que dependem de valores nulos. - Evite realocações frequentes; prefira dobrar a capacidade para amortizar o custo.
- Libere a memória alocada com
freequando não for mais necessária.
Referências
- cppreference: calloc
- cppreference: realloc
- cppreference: malloc
- GNU C Library: Allocating With Malloc
- Portugal a Programar: Alocação Dinâmica em C
- IME-USP: Alocação Dinâmica de Memória
Exercícios
- Escreva um programa que aloca um array de 10 inteiros usando
calloc, imprime os valores (que devem ser zero), e depois libera a memória. - Modifique o programa anterior para redimensionar o array para 20 elementos usando
realloc, preencha os novos elementos com valores (por exemplo, 100 a 109) e imprima tudo. - Crie uma função que recebe um ponteiro para um array de inteiros e seu tamanho, e dobra a capacidade do array usando
realloc. A função deve retornar o novo tamanho ou -1 em caso de erro. Teste a função. - Explique por que é importante usar um ponteiro temporário ao chamar
realloc. Dê um exemplo de código que ilustra o problema. - Escreva um programa que lê uma sequência de números do usuário (até o usuário digitar -1) e armazena em um array alocado dinamicamente. Use
callocpara alocar o array inicial com capacidade 5 ereallocpara expandir quando necessário. Imprima os números lidos.
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int*) calloc(10, sizeof(int));
if (arr == NULL) {
printf("Falha na alocação\n");
return 1;
}
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int*) calloc(10, sizeof(int));
if (arr == NULL) return 1;
// Preenche os 10 primeiros com 0 (já são) e depois redimensiona
int *temp = (int*) realloc(arr, 20 * sizeof(int));
if (temp == NULL) {
free(arr);
return 1;
}
arr = temp;
for (int i = 10; i < 20; i++) {
arr[i] = 100 + (i - 10);
}
for (int i = 0; i < 20; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int expandir_array(int **arr, int *tamanho) {
int novo_tamanho = (*tamanho) * 2;
int *temp = (int*) realloc(*arr, novo_tamanho * sizeof(int));
if (temp == NULL) {
return -1;
}
*arr = temp;
*tamanho = novo_tamanho;
return novo_tamanho;
}
int main() {
int *arr = (int*) malloc(5 * sizeof(int));
if (arr == NULL) return 1;
int tam = 5;
for (int i = 0; i < tam; i++) arr[i] = i;
int novo_tam = expandir_array(&arr, &tam);
if (novo_tam == -1) {
printf("Erro\n");
free(arr);
return 1;
}
printf("Novo tamanho: %d\n", novo_tam);
for (int i = 0; i < novo_tam; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
Usar um ponteiro temporário evita vazamento de memória se realloc falhar. Se atribuirmos diretamente ao ponteiro original e a realocação falhar, o ponteiro original se torna NULL e perdemos a referência ao bloco original, que não pode ser liberado. Exemplo incorreto:
int *arr = malloc(10 * sizeof(int));
// ...
arr = realloc(arr, 20 * sizeof(int)); // se falhar, arr = NULL e memória original vaza
if (arr == NULL) { /* erro, mas memória original perdida */ }
#include <stdio.h>
#include <stdlib.h>
int main() {
int capacidade = 5;
int *arr = (int*) calloc(capacidade, sizeof(int));
if (arr == NULL) return 1;
int count = 0;
int num;
printf("Digite números (-1 para sair):\n");
while (1) {
scanf("%d", &num);
if (num == -1) break;
if (count == capacidade) {
capacidade *= 2;
int *temp = (int*) realloc(arr, capacidade * sizeof(int));
if (temp == NULL) {
free(arr);
return 1;
}
arr = temp;
}
arr[count++] = num;
}
printf("Números lidos: ");
for (int i = 0; i < count; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}