C calloc e realloc

O malloc entrega um bloco de memória do tamanho pedido, com lixo dentro, e o tamanho fica fixo até o free. Duas funções irmãs resolvem as duas limitações. O calloc ("contiguous allocation", reserva contínua; muita gente lê como "clear allocation", reserva limpa) entrega o bloco zerado. O realloc ("re-allocate", reservar de novo) muda o tamanho de um bloco que já existe, para mais ou para menos, sem perder o que está dentro. Nesta aula você aprende as duas, com os cuidados que cada uma exige.

Você vai aprender
  • A reservar memória já zerada com calloc(quantidade, tamanho).
  • Por que o calloc é mais seguro com quantidades enormes (ele confere a multiplicação).
  • A aumentar e diminuir um bloco com realloc, e por que ele pode mudar o bloco de lugar.
  • O que fazem realloc(NULL, n) e realloc(p, 0) (este último, indefinido no C23).
  • Os dois erros clássicos do realloc: usar o endereço antigo e ler a parte nova sem dar valor a ela, como o gcc, o ASan e o Valgrind os mostram, e por que o editor do guia esconde os dois.

Esta aula continua malloc e free; leia aquela primeiro. Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa.


A ideia: o armário limpo e a troca de armário

Lembre do guarda-volumes da aula anterior. O malloc te dá um armário como o último cliente deixou. Agora o balcão oferece mais dois serviços:

  • "Quero 4 gavetas de 4 bytes, limpas." O atendente separa o armário, passa um pano em tudo e só então te entrega o número. É o calloc: você diz quantas coisas e o tamanho de cada uma, e recebe tudo zerado.
  • "Meu armário ficou pequeno." Se o armário do lado estiver vago, o atendente derruba a parede e o seu armário cresce ali mesmo. Se não estiver, ele pega um armário maior em outro corredor, muda as suas coisas para lá, libera o antigo e te entrega o número novo. É o realloc. O número antigo não vale mais, e o espaço a mais do armário novo vem sem limpeza.
FunçãoRecebeDevolveO conteúdo
malloc(bytes)quantos bytesum bloco novo, ou NULLlixo
calloc(quantidade, tamanho)quantos itens e o tamanho de cada umum bloco novo, ou NULLtudo zero
realloc(p, bytes)um bloco que já existe e o tamanho novoo bloco com o tamanho novo (talvez em outro lugar), ou NULLo conteúdo antigo é mantido; a parte nova é lixo

1. calloc: memória que começa zerada

A Ana é mesária na eleição do grêmio da escola. São 4 candidatos, numerados de 0 a 3, e cada voto soma 1 no contador do candidato. Contadores precisam começar em zero, e é isso que o calloc garante:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int candidatos = 4;
    int *votos = calloc(candidatos, sizeof *votos);
    if (votos == NULL) {
        printf("Faltou memória!\n");
        return 1;
    }

    int urna[] = {2, 0, 2, 3, 1, 2, 0, 2};
    int total = sizeof urna / sizeof urna[0];
    for (int i = 0; i < total; i++) {
        votos[urna[i]]++;
    }

    for (int c = 0; c < candidatos; c++) {
        printf("Candidato %d: %d votos\n", c, votos[c]);
    }
    free(votos);
    return 0;
}
calloc(candidatos, sizeof *votos)
Dois argumentos, separados por vírgula: quantos itens (4) e o tamanho de cada um (o de um int, 4 bytes). O calloc faz a conta, reserva 16 bytes e põe zero em todos. Compare com o malloc, que recebe a conta já feita: malloc(candidatos * sizeof *votos).
if (votos == NULL)
Como o malloc, o calloc devolve NULL quando não consegue. A conferência é a mesma.
int total = sizeof urna / sizeof urna[0];
Quantos votos há na urna: o tamanho do array inteiro dividido pelo tamanho de um item (aula sizeof). Aqui, 8.
votos[urna[i]]++;
Lê de dentro para fora: urna[i] é o número do candidato que recebeu o voto i; votos[…]++ soma 1 no contador dele. Só funciona porque cada contador começou em 0.
free(votos);
Um bloco do calloc é devolvido com o mesmo free de sempre.
$ gcc -std=c23 -Wall -Wextra votos.c -o votos
$ ./votos
Candidato 0: 2 votos
Candidato 1: 1 votos
Candidato 2: 4 votos
Candidato 3: 1 votos

E se fosse malloc?

Com malloc, o erro é traiçoeiro: às vezes o bloco vem zerado por sorte (memória que o sistema acabou de entregar ao programa costuma vir com zeros), e o programa parece certo. Esta versão apura duas urnas, a da manhã e a da tarde, cada uma com o seu bloco do malloc:

#include <stdio.h>
#include <stdlib.h>

#define CANDIDATOS 6

void apurar(const char *nome, const int *urna, int total) {
    int *votos = malloc(CANDIDATOS * sizeof *votos);
    if (votos == NULL) {
        return;
    }
    for (int i = 0; i < total; i++) {
        votos[urna[i]]++;
    }
    printf("%s:", nome);
    for (int c = 0; c < CANDIDATOS; c++) {
        printf(" %d", votos[c]);
    }
    printf("\n");
    free(votos);
}

int main(void) {
    int manha[] = {0, 1, 1, 2, 3, 4, 5, 5};
    int tarde[] = {5, 4, 3, 2, 1, 0};
    apurar("Manhã", manha, 8);
    apurar("Tarde", tarde, 6);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra urnas.c -o urnas      « nenhum aviso
$ ./urnas
Manhã: 1 2 1 1 1 2
Tarde: 1560991578 6 1 1 2 3

A manhã saiu certa: o bloco era memória nova, cheia de zeros. A tarde saiu absurda: o segundo malloc recebeu o mesmo bloco que a manhã tinha acabado de devolver, ainda com as contagens da manhã (e com as anotações internas do malloc por cima das primeiras). Cada candidato recebeu um voto à tarde; o resultado certo seria 1 1 1 1 1 1. No editor do guia o erro também aparece, com outra cara: Tarde: 2 3 2 2 2 3, as contagens da manhã somadas às da tarde. Nenhum compilador avisa, porque o problema só existe durante a execução.

Missão: rode e veja a apuração da tarde sair errada. Troque o malloc de apurar por um calloc (lembre: dois argumentos, a quantidade e o tamanho de cada item). A saída deve mostrar Tarde: 1 1 1 1 1 1.

#include <stdio.h>
#include <stdlib.h>

#define CANDIDATOS 6

void apurar(const char *nome, const int *urna, int total) {
    int *votos = malloc(CANDIDATOS * sizeof *votos);
    if (votos == NULL) {
        return;
    }
    for (int i = 0; i < total; i++) {
        votos[urna[i]]++;
    }
    printf("%s:", nome);
    for (int c = 0; c < CANDIDATOS; c++) {
        printf(" %d", votos[c]);
    }
    printf("\n");
    free(votos);
}

int main(void) {
    int manha[] = {0, 1, 1, 2, 3, 4, 5, 5};
    int tarde[] = {5, 4, 3, 2, 1, 0};
    apurar("Manhã", manha, 8);
    apurar("Tarde", tarde, 6);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

#define CANDIDATOS 6

void apurar(const char *nome, const int *urna, int total) {
    int *votos = calloc(CANDIDATOS, sizeof *votos);
    if (votos == NULL) {
        return;
    }
    for (int i = 0; i < total; i++) {
        votos[urna[i]]++;
    }
    printf("%s:", nome);
    for (int c = 0; c < CANDIDATOS; c++) {
        printf(" %d", votos[c]);
    }
    printf("\n");
    free(votos);
}

int main(void) {
    int manha[] = {0, 1, 1, 2, 3, 4, 5, 5};
    int tarde[] = {5, 4, 3, 2, 1, 0};
    apurar("Manhã", manha, 8);
    apurar("Tarde", tarde, 6);
    return 0;
}

A outra forma: malloc + memset

Também dá para zerar um bloco do malloc depois, com o memset ("memory set", preencher memória) da string.h:

int *votos = malloc(CANDIDATOS * sizeof *votos);
if (votos == NULL) {
    return;
}
memset(votos, 0, CANDIDATOS * sizeof *votos);

O memset(votos, 0, bytes) escreve o byte 0 em cada um dos bytes bytes a partir de votos. O resultado é igual ao do calloc, mas em duas linhas e com a conta repetida. Prefira o calloc quando o bloco é novo; o memset serve para zerar de novo um bloco que já está em uso (por exemplo, para apurar outra urna no mesmo bloco).

Zero em todos os bytes = 0, 0.0 e NULL

O calloc (e o memset com 0) zeram bytes. Para int e char, isso é o número 0 em qualquer computador. Para double e para ponteiros, a norma do C não garante, mas em todas as máquinas comuns de hoje (Linux, Windows, macOS, o editor do guia) todos os bytes zero também são 0.0 e NULL.


2. O calloc confere a multiplicação

Há um segundo motivo para preferir o calloc, e ele é de segurança. O malloc(n * sizeof *p) faz a conta antes de chamar a função, com o tipo size_t, que tem um limite (SIZE_MAX). Se n for absurdo, a conta passa do limite e dá a volta, como um hodômetro que passa de 999999 para 000000 (aula Inteiros). O calloc recebe os dois números separados e confere a conta por dentro:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main(void) {
    /* uma quantidade absurda, como a que viria de um arquivo estragado */
    size_t n = SIZE_MAX / sizeof(int) + 2;

    printf("n * sizeof(int) = %zu bytes\n", n * sizeof(int));

    int *a = malloc(n * sizeof *a);
    printf("malloc: %s\n", a == NULL ? "NULL" : "devolveu um bloco!");

    int *b = calloc(n, sizeof *b);
    printf("calloc: %s\n", b == NULL ? "NULL" : "devolveu um bloco!");

    free(a);
    free(b);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra estouro.c -o estouro
estouro.c: In function ‘main’:
estouro.c:14:14: warning: product ‘4611686018427387905 * 4’ of arguments 1 and 2 exceeds ‘SIZE_MAX’ [-Walloc-size-larger-than=]
   14 |     int *b = calloc(n, sizeof *b);
      |              ^~~~~~~~~~~~~~~~~~~~
In file included from estouro.c:3:
/usr/include/stdlib.h:679:14: note: in a call to allocation function ‘calloc’ declared here
  679 | extern void *calloc (size_t __nmemb, size_t __size)
      |              ^~~~~~
$ ./estouro
n * sizeof(int) = 4 bytes
malloc: devolveu um bloco!
calloc: NULL
SIZE_MAX / sizeof(int) + 2
SIZE_MAX (de stdint.h) é o maior size_t. Esta quantidade de int ocupa um pouco mais de bytes do que existe: 4 bytes além do limite. Na vida real, um número assim vem de um arquivo estragado ou de alguém mal-intencionado.
n * sizeof(int) = 4 bytes
A conta deu a volta e sobrou 4. O malloc recebeu esse 4 e, feliz, entregou um bloco de 4 bytes. Um programa que agora gravasse n inteiros nesse bloco escreveria muito além do fim: é assim que muitos ataques a programas em C começam.
calloc: NULL
O calloc percebeu que n × 4 não cabe num size_t e devolveu NULL, como se faltasse memória. O if de sempre trata o caso.
warning: product … exceeds ‘SIZE_MAX’ [-Walloc-size-larger-than=]
"O produto dos argumentos 1 e 2 passa do SIZE_MAX". Aqui o gcc conseguiu fazer a conta na compilação, porque n é fixo. Repare que ele avisou sobre o calloc (que vê os dois números) e não sobre o malloc (que só vê o 4 já estragado). Quando o número vem de fora, durante a execução, não há aviso nenhum.

No editor do guia, a saída é a mesma (lá o size_t tem 4 bytes, e a conta também dá a volta para 4), sem o aviso. Se você precisar usar malloc com um n que vem de fora, confira antes: if (n > SIZE_MAX / sizeof *p) { /* grande demais */ }.


3. realloc: um bloco maior

A lista de compras da Ana tinha 3 preços, e ela lembrou de mais 2. Em vez de reservar outro bloco e copiar tudo à mão, o realloc aumenta o bloco que já existe:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    double *precos = malloc(3 * sizeof *precos);
    if (precos == NULL) {
        return 1;
    }
    precos[0] = 12.50;
    precos[1] = 4.99;
    precos[2] = 7.80;
    printf("Antes:  %p\n", (void *)precos);

    double *maior = realloc(precos, 5 * sizeof *precos);
    if (maior == NULL) {
        free(precos);
        return 1;
    }
    precos = maior;
    printf("Depois: %p\n", (void *)precos);

    precos[3] = 3.25;
    precos[4] = 9.90;
    for (int i = 0; i < 5; i++) {
        printf("Item %d: R$ %.2f\n", i + 1, precos[i]);
    }
    free(precos);
    return 0;
}
realloc(precos, 5 * sizeof *precos)
Dois argumentos: o bloco atual e o tamanho novo, total, em bytes (não "quantos a mais"). Aqui, 5 preços de 8 bytes: 40 bytes.
double *maior = …
O resultado vai para uma variável nova. Se o realloc falhar, ele devolve NULL e não mexe no bloco antigo; com a variável auxiliar, precos continua apontando para os 3 preços, que ainda podem ser usados ou liberados. Escrever precos = realloc(precos, …) perderia o bloco antigo numa falha (a aula Array que Cresce mostra esse vazamento no Valgrind).
if (maior == NULL) { free(precos); return 1; }
Não deu para crescer: o programa libera o bloco antigo, que ainda é dele, e desiste.
precos = maior;
Deu certo: daqui em diante a lista é o bloco que o realloc devolveu. Os 3 preços antigos estão lá dentro, nas mesmas posições.
precos[3] = 3.25; precos[4] = 9.90;
As posições novas existem, mas vêm sem valor (seção 6): o programa dá valor a elas antes de ler.
free(precos);
Um free só, do bloco atual. O antigo, se o realloc trocou de lugar, já foi liberado por ele.
$ gcc -std=c23 -Wall -Wextra cresce.c -o cresce
$ ./cresce
Antes:  0x55e538acd010
Depois: 0x55e538ace040      « o bloco mudou de lugar
Item 1: R$ 12.50
Item 2: R$ 4.99
Item 3: R$ 7.80
Item 4: R$ 3.25
Item 5: R$ 9.90

Os dois endereços são diferentes: o espaço logo depois do bloco estava ocupado (pelo buffer que o primeiro printf reservou para ele mesmo), então o realloc pegou um armário em outro lugar, copiou os 3 preços e liberou o antigo. No editor do guia, os dois endereços saem iguais (0x801670): lá havia espaço livre logo depois, e o bloco cresceu ali mesmo. Os dois comportamentos estão certos, e o seu programa não tem como saber qual vai acontecer. Por isso a regra: depois do realloc, use só o ponteiro que ele devolveu.

Por que não reservar já o tamanho grande?

Quando você sabe o tamanho final, reserve de uma vez. O realloc é para quando não sabe: itens chegando um a um, um arquivo de tamanho desconhecido, o usuário que vai digitando. Cada troca de lugar copia o bloco inteiro, então chamar realloc para cada item novo fica lento com muitos itens. A aula Array que Cresce mostra a técnica usada na prática: dobrar a capacidade a cada vez que lota.


4. realloc para diminuir

O realloc também encolhe. A Ana reservou espaço para 100 notas, mas a turma só tinha 3. Para devolver a sobra:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *notas = malloc(100 * sizeof *notas);
    if (notas == NULL) {
        return 1;
    }
    int usadas = 3;
    notas[0] = 7;
    notas[1] = 9;
    notas[2] = 8;
    printf("Antes:  %p (100 lugares)\n", (void *)notas);

    int *justo = realloc(notas, usadas * sizeof *notas);
    if (justo != NULL) {
        notas = justo;
    }
    printf("Depois: %p (%d lugares)\n", (void *)notas, usadas);
    for (int i = 0; i < usadas; i++) {
        printf("%d ", notas[i]);
    }
    printf("\n");
    free(notas);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra encolhe.c -o encolhe
$ ./encolhe
Antes:  0x560a58a08010 (100 lugares)
Depois: 0x560a58a08010 (3 lugares)      « encolheu no mesmo lugar
7 9 8

Os 3 primeiros valores ficam; o resto do bloco é devolvido. Encolher quase sempre acontece no mesmo lugar, mas a norma do C não promete: use o ponteiro devolvido, como sempre. Repare no if (justo != NULL): se o realloc falhasse ao encolher (raríssimo), o bloco de 100 continuaria valendo, e o programa segue com ele. Para poucos bytes, encolher não vale o trabalho; vale para blocos grandes que vão ficar muito tempo na memória.


5. Os casos especiais: NULL e zero

realloc(NULL, n) é um malloc(n)

Se o primeiro argumento é NULL, o realloc simplesmente reserva um bloco novo. Isso deixa o código de uma lista que cresce mais simples: o ponteiro começa em NULL e o mesmo realloc serve para a primeira reserva e para as seguintes (veja Array que Cresce).

realloc(p, 0): não use C23

Pedir tamanho zero é uma armadilha. Até o C17, cada biblioteca decidia o que fazer; no C23 virou comportamento indefinido, ou seja, proibido. Veja como as duas bibliotecas deste guia respondem ao mesmo programa:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *v = malloc(4 * sizeof *v);
    if (v == NULL) {
        return 1;
    }
    v[0] = 1;
    int *r = realloc(v, 0);
    printf("realloc(v, 0) devolveu %s\n", r == NULL ? "NULL" : "um ponteiro");
    free(r);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra zero.c -o zero      « nenhum aviso
$ ./zero
realloc(v, 0) devolveu NULL

A glibc liberou o bloco e devolveu NULL. No editor do guia, a wasi-libc mostra realloc(v, 0) devolveu um ponteiro: um bloco novo, de tamanho zero. Um programa que confia num dos dois comportamentos quebra no outro: com a glibc, um if (r == NULL) que pensa "falhou, v ainda vale" acaba usando um bloco já liberado. Para liberar, use free(p). Para encolher, peça pelo menos 1 item.


6. Depois do realloc, o endereço antigo morre

O erro mais comum com realloc acontece quando ele fica dentro de uma função. A Ana escreveu uma função que acrescenta um preço à lista:

#include <stdio.h>
#include <stdlib.h>

void adicionar(double *lista, int n, double preco) {
    double *maior = realloc(lista, (n + 1) * sizeof *lista);
    if (maior == NULL) {
        return;
    }
    maior[n] = preco;
}

int main(void) {
    double *precos = malloc(3 * sizeof *precos);
    if (precos == NULL) {
        return 1;
    }
    precos[0] = 12.50;
    precos[1] = 4.99;
    precos[2] = 7.80;
    printf("Lista com 3 itens.\n");

    adicionar(precos, 3, 9.90);

    for (int i = 0; i < 4; i++) {
        printf("Item %d: R$ %.2f\n", i + 1, precos[i]);
    }
    free(precos);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra adicionar.c -o adicionar      « nenhum aviso
$ ./adicionar
Lista com 3 itens.
Item 1: R$ 0.00
Item 2: R$ 0.00
Item 3: R$ 0.00
Item 4: R$ 0.00

Os preços sumiram. O que houve: lista é uma cópia do endereço que estava em precos (parâmetros recebem cópias; aula Passar por Ponteiro). O realloc mudou o bloco de lugar, liberou o antigo e devolveu o endereço novo em maior, uma variável local que morre no fim da função. O precos do main nunca soube da mudança: continua apontando para o bloco liberado. Três erros de uma vez: o main lê memória liberada, dá free num bloco já liberado e o bloco novo fica perdido. O ASan mostra o primeiro deles, e conta quem liberou:

$ gcc -std=c23 -Wall -Wextra -g -fsanitize=address adicionar.c -o adicionar-asan
$ ./adicionar-asan
=================================================================
==432198==ERROR: AddressSanitizer: heap-use-after-free on address 0x7ba3095e0040 at pc 0x55bea7cf743a bp 0x7ffc413b9b30 sp 0x7ffc413b9b20
READ of size 8 at 0x7ba3095e0040 thread T0
    #0 0x55bea7cf7439 in main /home/ana/c/adicionar.c:25
«…»
0x7ba3095e0040 is located 0 bytes inside of 24-byte region [0x7ba3095e0040,0x7ba3095e0058)
freed by thread T0 here:
    #0 0x7f730a72a6cf in realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:81
    #1 0x55bea7cf72bf in adicionar /home/ana/c/adicionar.c:5
    #2 0x55bea7cf73fc in main /home/ana/c/adicionar.c:22
«…»
previously allocated by thread T0 here:
    #0 0x7f730a72b60f in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:67
    #1 0x55bea7cf7322 in main /home/ana/c/adicionar.c:13
«… o resto do relatório …»

Leitura de 8 bytes (um double) na linha 25, o printf, num bloco de 24 bytes (os 3 preços) que foi liberado pelo realloc (freed by thread T0 here: … in realloc) dentro de adicionar, na linha 5. É a pista típica deste erro: "liberado por realloc" quer dizer "alguém continuou usando o endereço de antes".

No editor do guia, o programa parece certo

O mesmo adicionar.c no editor mostra os 4 preços certinhos (Item 4: R$ 9.90) e termina com 0. Lá, o bloco cresceu no mesmo lugar, então o endereço antigo continuou valendo por acaso. O erro está no código do mesmo jeito: basta o realloc precisar mudar de lugar. Teste programas com realloc no seu Linux, com -fsanitize=address (que, de propósito, muda o bloco de lugar sempre, para pegar este erro).

O conserto: a função devolve o endereço novo, e quem chama guarda. Se o realloc falhar, a função devolve NULL, e quem chama ainda tem a lista antiga (a outra forma, passar o endereço do ponteiro, está em Ponteiro para Ponteiro).

Missão: aqui no editor o programa até mostra os 4 preços, mas você já sabe que é sorte. Conserte: mude adicionar para devolver double * (o maior, ou NULL se o realloc falhar) e, no main, guarde o resultado numa variável (double *nova = adicionar(precos, 3, 9.90);), confira o NULL e só então faça precos = nova;.

#include <stdio.h>
#include <stdlib.h>

void adicionar(double *lista, int n, double preco) {
    double *maior = realloc(lista, (n + 1) * sizeof *lista);
    if (maior == NULL) {
        return;
    }
    maior[n] = preco;
}

int main(void) {
    double *precos = malloc(3 * sizeof *precos);
    if (precos == NULL) {
        return 1;
    }
    precos[0] = 12.50;
    precos[1] = 4.99;
    precos[2] = 7.80;
    printf("Lista com 3 itens.\n");

    adicionar(precos, 3, 9.90);

    for (int i = 0; i < 4; i++) {
        printf("Item %d: R$ %.2f\n", i + 1, precos[i]);
    }
    free(precos);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

double *adicionar(double *lista, int n, double preco) {
    double *maior = realloc(lista, (n + 1) * sizeof *lista);
    if (maior == NULL) {
        return NULL;
    }
    maior[n] = preco;
    return maior;
}

int main(void) {
    double *precos = malloc(3 * sizeof *precos);
    if (precos == NULL) {
        return 1;
    }
    precos[0] = 12.50;
    precos[1] = 4.99;
    precos[2] = 7.80;
    printf("Lista com 3 itens.\n");

    double *nova = adicionar(precos, 3, 9.90);
    if (nova == NULL) {
        free(precos);
        return 1;
    }
    precos = nova;

    for (int i = 0; i < 4; i++) {
        printf("Item %d: R$ %.2f\n", i + 1, precos[i]);
    }
    free(precos);
    return 0;
}

7. A parte nova não vem zerada

O realloc copia o conteúdo antigo, mas o espaço a mais vem como o malloc entrega: com lixo. Isso vale até para um bloco que nasceu do calloc. Aqui, 2 contadores zerados crescem para 8:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *votos = calloc(2, sizeof *votos);
    if (votos == NULL) {
        return 1;
    }

    int *rascunho = malloc(8 * sizeof *rascunho);
    if (rascunho == NULL) {
        free(votos);
        return 1;
    }
    for (int i = 0; i < 8; i++) {
        rascunho[i] = 99;
    }
    free(rascunho);

    int *maior = realloc(votos, 8 * sizeof *votos);
    if (maior == NULL) {
        free(votos);
        return 1;
    }
    votos = maior;
    for (int i = 0; i < 8; i++) {
        printf("%d ", votos[i]);
    }
    printf("\n");
    free(votos);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra -g novoslugares.c -o novoslugares
$ ./novoslugares
0 0 0 0 0 0 0 0

No gcc, saiu tudo zero, por sorte: o realloc da glibc pegou memória nova do sistema. No editor do guia, o mesmo programa mostra 0 0 0 59777 99 99 99 99: os dois contadores antigos zerados, e depois o lixo deixado pelo rascunho. O Valgrind não depende da sorte: ele sabe que os 6 contadores novos nunca receberam valor, e reclama quando o printf da linha 27 os usa. Com --track-origins=yes, ele diz de onde veio o valor sem dono:

$ valgrind --track-origins=yes ./novoslugares
«… o cabeçalho …»
==432294== Use of uninitialised value of size 8
==432294==    at 0x48CD062: _itoa_word (_fitoa_word.c:38)
==432294==    by 0x48D861C: __printf_buffer (vfprintf-process-arg.c:155)
==432294==    by 0x48DA767: __vfprintf_internal (vfprintf-internal.c:1548)
==432294==    by 0x48CE132: printf (printf.c:33)
==432294==    by 0x40012E8: main (novoslugares.c:27)
==432294==  Uninitialised value was created by a heap allocation
==432294==    at 0x485807F: realloc (vg_replace_malloc.c:1804)
==432294==    by 0x400128D: main (novoslugares.c:20)
«… mais erros parecidos, todos com a mesma origem …»

"Valor não inicializado, criado por uma reserva no heap: realloc, na linha 20." O conserto é dar valor aos lugares novos logo depois do realloc, com um laço (for (int i = 2; i < 8; i++) votos[i] = 0;) ou com memset(votos + 2, 0, 6 * sizeof *votos);. Repare no votos + 2: o endereço do terceiro contador, o primeiro lugar novo (aula Ponteiros e Arrays).


Qual usar

SituaçãoUse
Bloco novo que você vai preencher inteiro logo em seguida (lendo do teclado, copiando)malloc(n * sizeof *p)
Bloco novo que precisa começar com zeros (contadores, somas, marcações "ainda não visto")calloc(n, sizeof *p)
Quantidade n vinda de fora (usuário, arquivo, rede)calloc, ou malloc com a conferência n > SIZE_MAX / sizeof *p
Zerar de novo um bloco que já existememset(p, 0, n * sizeof *p)
Aumentar ou diminuir um bloco, mantendo o conteúdorealloc, sempre numa variável auxiliar
Uma lista que cresce item a itemrealloc dobrando a capacidade (aula Array que Cresce)
Liberarfree(p), nunca realloc(p, 0)

Quando dá errado

Os valores somem ou viram lixo depois de uma função que chama realloc

O que significa: a função fez o realloc numa cópia do ponteiro, e quem chamou continua com o endereço antigo, já liberado. No gcc aparecem valores errados (como R$ 0.00) ou um aborto no free; no editor do guia, o programa costuma parecer certo. O ASan mostra heap-use-after-free com freed by thread T0 here: … in realloc.

Como consertar: a função devolve o ponteiro novo (precos = nova; em quem chama, depois de conferir o NULL) ou recebe o endereço do ponteiro (double **). Nunca guarde endereços de dentro do bloco (&precos[0]) de antes de um realloc; guarde índices.

Use of uninitialised value … created by a heap allocation … realloc

O que significa: o programa leu a parte nova de um bloco que cresceu, antes de dar valor a ela. O realloc não zera, nem quando o bloco veio do calloc.

Como consertar: logo depois do realloc, dê valor às posições novas (laço ou memset).

warning: product ‘…’ of arguments 1 and 2 exceeds ‘SIZE_MAX’ [-Walloc-size-larger-than=]

O que significa: o gcc fez a conta quantidade × tamanho na compilação e viu que ela não cabe num size_t. O calloc vai devolver NULL.

Como consertar: confira o número de itens; quase sempre é uma conta errada ou um valor de teste absurdo. Sem o aviso (quando o número vem de fora), o if (p == NULL) trata o caso.

error: too few arguments to function ‘calloc’; expected 2, have 1
$ gcc -std=c23 -Wall -Wextra umarg.c -o umarg
umarg.c: In function ‘main’:
umarg.c:4:18: error: too few arguments to function ‘calloc’; expected 2, have 1
    4 |     int *votos = calloc(4 * sizeof *votos);
      |                  ^~~~~~
In file included from umarg.c:1:
/usr/include/stdlib.h:679:14: note: declared here
  679 | extern void *calloc (size_t __nmemb, size_t __size)
      |              ^~~~~~

O que significa: "poucos argumentos para a função calloc: esperava 2, recebeu 1". O calloc foi chamado como se fosse o malloc, com a conta já feita. A note mostra a declaração da glibc, com os dois parâmetros (__nmemb, "número de membros", e __size, o tamanho de cada um). No editor, o Clang diz error: too few arguments to function call, expected 2, have 1.

Como consertar: separe a quantidade do tamanho com uma vírgula: calloc(4, sizeof *votos).

O programa se comporta diferente no editor e no Linux depois de realloc(p, 0)

O que significa: o tamanho zero é comportamento indefinido no C23: a glibc libera e devolve NULL; a wasi-libc do editor devolve um bloco novo.

Como consertar: para liberar, free(p). Se o tamanho novo vem de uma conta, trate o zero antes de chamar o realloc.


Referência rápida

CódigoO que faz
void *calloc(size_t n, size_t tamanho);Reserva n itens de tamanho bytes, todos os bytes zero; NULL se não deu ou se n × tamanho não cabe num size_t.
void *realloc(void *p, size_t bytes);Muda o bloco p para bytes bytes (o total). Mantém o conteúdo até o menor dos dois tamanhos; a parte nova é lixo. Pode mudar de lugar. NULL se não deu, e aí p continua valendo.
T *maior = realloc(p, n * sizeof *p);
if (maior != NULL) p = maior;
O jeito seguro: só troca o ponteiro quando deu certo.
realloc(NULL, bytes)O mesmo que malloc(bytes).
realloc(p, 0)Comportamento indefinido no C23. Use free(p).
memset(p, 0, bytes)Põe zero em bytes bytes a partir de p (string.h).
SIZE_MAXO maior size_t (stdint.h).

Exercícios

1. Contadores zerados

Complete para reservar 10 contadores int que começam em zero:

int *cont = calloc();

2. O tamanho novo

A lista notas tem 5 double. Complete para que ela passe a ter 8:

double *maior = realloc(notas, );

3. O malloc disfarçado

Qual valor no primeiro argumento faz o realloc funcionar igual ao malloc?

realloc(, 40)

4. Por que calloc(n, 4) é mais seguro que malloc(n * 4) quando n vem de um arquivo?

5. Depois de int *maior = realloc(v, 10 * sizeof *v); dar certo, o que é verdade?

6. Um programa com realloc dentro de uma função funciona no editor do guia e mostra lixo no gcc. Qual é a explicação mais provável?

Missão final: o candidato que chegou atrasado

Missão: a eleição começou com 3 candidatos e, à tarde, entrou o candidato 3. (1) Reserve os n contadores com calloc, no lugar do NULL. (2) No ponto marcado, aumente o bloco para n + 1 contadores com realloc, numa variável auxiliar (se falhar, libere votos e saia com 1), dê zero ao contador novo (maior[n] = 0; ou votos[n] = 0;) e some 1 em n. A saída deve ser:

Candidato 0: 2 votos
Candidato 1: 2 votos
Candidato 2: 3 votos
Candidato 3: 2 votos
#include <stdio.h>
#include <stdlib.h>

void mostrar(const int *votos, int n) {
    for (int c = 0; c < n; c++) {
        printf("Candidato %d: %d votos\n", c, votos[c]);
    }
}

int main(void) {
    int n = 3;
    int *votos = NULL;   /* (1) reserve n contadores zerados */
    if (votos == NULL) {
        printf("Faltou memória!\n");
        return 1;
    }

    int manha[] = {0, 2, 1, 2, 2};
    for (int i = 0; i < 5; i++) {
        votos[manha[i]]++;
    }

    /* (2) entrou o candidato 3: cresça para n + 1, zere o novo e some 1 em n */

    int tarde[] = {3, 1, 3, 0};
    for (int i = 0; i < 4; i++) {
        votos[tarde[i]]++;
    }

    mostrar(votos, n);
    free(votos);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

void mostrar(const int *votos, int n) {
    for (int c = 0; c < n; c++) {
        printf("Candidato %d: %d votos\n", c, votos[c]);
    }
}

int main(void) {
    int n = 3;
    int *votos = calloc(n, sizeof *votos);
    if (votos == NULL) {
        printf("Faltou memória!\n");
        return 1;
    }

    int manha[] = {0, 2, 1, 2, 2};
    for (int i = 0; i < 5; i++) {
        votos[manha[i]]++;
    }

    int *maior = realloc(votos, (n + 1) * sizeof *votos);
    if (maior == NULL) {
        free(votos);
        return 1;
    }
    votos = maior;
    votos[n] = 0;
    n++;

    int tarde[] = {3, 1, 3, 0};
    for (int i = 0; i < 4; i++) {
        votos[tarde[i]]++;
    }

    mostrar(votos, n);
    free(votos);
    return 0;
}

Para conferir o que o editor não confere, rode a sua solução no Linux com gcc -g -fsanitize=address e com valgrind --leak-check=full: nenhum erro, e All heap blocks were freed -- no leaks are possible. Depois apague o votos[n] = 0; e rode no Valgrind de novo: aparece o Conditional jump or move depends on uninitialised value(s) da seção 7.


Resumo

  • calloc(quantidade, tamanho) reserva um bloco com todos os bytes zero. Use para contadores e somas. A alternativa é malloc + memset(p, 0, bytes).
  • O calloc confere se quantidade × tamanho cabe num size_t; o malloc(n * tamanho) não, e a conta pode dar a volta.
  • realloc(p, bytes) muda o tamanho de um bloco (para mais ou para menos), mantendo o conteúdo. Ele pode mudar o bloco de lugar e liberar o antigo: use só o ponteiro devolvido, guardado numa variável auxiliar até conferir o NULL.
  • A parte nova de um bloco que cresceu vem com lixo, mesmo se ele nasceu do calloc.
  • realloc(NULL, n) = malloc(n). realloc(p, 0) é indefinido no C23: use free.
  • Uma função que faz realloc precisa devolver o ponteiro novo a quem chamou.
  • No editor do guia, o bloco costuma crescer no mesmo lugar, e o erro do endereço antigo fica escondido. No Linux, o ASan (freed by … realloc) e o Valgrind mostram.