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.
- 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)erealloc(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ção | Recebe | Devolve | O conteúdo |
|---|---|---|---|
malloc(bytes) | quantos bytes | um bloco novo, ou NULL | lixo |
calloc(quantidade, tamanho) | quantos itens e o tamanho de cada um | um bloco novo, ou NULL | tudo zero |
realloc(p, bytes) | um bloco que já existe e o tamanho novo | o bloco com o tamanho novo (talvez em outro lugar), ou NULL | o 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). Ocallocfaz a conta, reserva 16 bytes e põe zero em todos. Compare com omalloc, que recebe a conta já feita:malloc(candidatos * sizeof *votos). - if (votos == NULL)
- Como o
malloc, ocallocdevolveNULLquando 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 votoi;votos[…]++soma 1 no contador dele. Só funciona porque cada contador começou em 0. - free(votos);
- Um bloco do
callocé devolvido com o mesmofreede 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).
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(destdint.h) é o maiorsize_t. Esta quantidade deintocupa 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
mallocrecebeu esse 4 e, feliz, entregou um bloco de 4 bytes. Um programa que agora gravasseninteiros nesse bloco escreveria muito além do fim: é assim que muitos ataques a programas em C começam. - calloc: NULL
- O
callocpercebeu quen × 4não cabe numsize_te devolveuNULL, como se faltasse memória. Oifde 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, porquené fixo. Repare que ele avisou sobre ocalloc(que vê os dois números) e não sobre omalloc(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
reallocfalhar, ele devolveNULLe não mexe no bloco antigo; com a variável auxiliar,precoscontinua apontando para os 3 preços, que ainda podem ser usados ou liberados. Escreverprecos = 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
reallocdevolveu. 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
freesó, do bloco atual. O antigo, se orealloctrocou 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".
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ção | Use |
|---|---|
| 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á existe | memset(p, 0, n * sizeof *p) |
| Aumentar ou diminuir um bloco, mantendo o conteúdo | realloc, sempre numa variável auxiliar |
| Uma lista que cresce item a item | realloc dobrando a capacidade (aula Array que Cresce) |
| Liberar | free(p), nunca realloc(p, 0) |
Quando dá errado
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).
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ódigo | O 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_MAX | O maior size_t (stdint.h). |
Exercícios
1. Contadores zerados
Complete para reservar 10 contadores int que começam em zero:
2. O tamanho novo
A lista notas tem 5 double. Complete para que ela passe a ter 8:
3. O malloc disfarçado
Qual valor no primeiro argumento faz o realloc funcionar igual ao malloc?
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
callocconfere sequantidade × tamanhocabe numsize_t; omalloc(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 oNULL.- 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: usefree.- Uma função que faz
reallocprecisa 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.