malloc responde a uma pergunta: me dê esta quantidade de bytes. Dois companheiros em <stdlib.h> respondem às perguntas que vêm em seguida - me dê esta quantidade de bytes, limpos (calloc), e preciso que o bloco que já tenho fique maior (realloc).
calloc: contagem, tamanho e zeros
void *calloc(size_t count, size_t size);
Dois argumentos em vez de um, e o resultado vem preenchido com bytes zero.
Esse é o caso clássico: um histograma ou array de contagens em que toda posição precisa começar em zero. Com malloc você precisaria de um laço; calloc faz isso como parte da alocação, muitas vezes de graça, porque o sistema operacional já entrega páginas novas pré-zeradas.
O argumento do overflow
A divisão em dois argumentos não é cosmética. Considere uma contagem lida de um arquivo:
size_t n = huge_value_from_input;
int *a = malloc(n * sizeof *a); /* o produto pode dar a volta */
int *b = calloc(n, sizeof *b); /* obrigado a detectar o overflow */
Se n * sizeof *a exceder o que um size_t pode guardar, ele dá a volta para um número pequeno, malloc tem sucesso com um bloco minúsculo, e toda escrita subsequente vai muito além do fim. O padrão exige que calloc falhe e retorne NULL nesse caso. Quando um tamanho vem de fora do seu programa, essa checagem vale a pena.
O que "zerado" realmente significa
calloc escreve bytes zero. Para inteiros e caracteres isso é o valor zero, que é o que você quer. Para ponteiros e valores de ponto flutuante, todos os bits em zero é um ponteiro nulo e 0.0 em toda plataforma relevante, mas o padrão C não promete isso. Código que precisa ser estritamente portável atribui esses valores explicitamente.
calloc vs malloc + memset
int *a = calloc(n, sizeof *a);
int *b = malloc(n * sizeof *b);
if (b != NULL) {
memset(b, 0, n * sizeof *b);
}
Eles produzem o mesmo resultado. Prefira calloc: é uma linha, faz a checagem de overflow, e para blocos grandes pode evitar tocar a memória por completo. Prefira o malloc simples quando você vai sobrescrever cada byte de qualquer forma - zerar um megabyte que você vai preencher imediatamente é desperdício puro.
realloc: mudando o tamanho
void *realloc(void *p, size_t newsize);
realloc devolve um bloco de newsize bytes cujo conteúdo corresponde ao bloco antigo até o menor dos dois tamanhos. Ele pode estender o bloco onde ele está, ou alocar um novo, copiar e liberar o antigo. Você não tem como saber qual dos dois, então o único ponteiro que você pode usar depois é o que ele retornou.
O idioma do ponteiro tmp
As três linhas em torno de tmp são a razão inteira de essa função ter uma reputação. O atalho tentador é defeituoso:
/* BUG: vaza o bloco original quando realloc falha */
p = realloc(p, newsize);
if (p == NULL) {
return;
}
Quando realloc falha ele retorna NULL e deixa o bloco original alocado e inalterado. Atribuir o resultado direto em p sobrescreve o único ponteiro para aquele bloco, então ele nunca poderá ser liberado - um vazamento, e um que acontece exatamente quando a memória já está escassa. Atribua a tmp, teste tmp, e só então efetive:
void *tmp = realloc(p, newsize);
if (tmp == NULL) {
/* p ainda é válido; trate a falha como convier a quem chamou */
return 0;
}
p = tmp;
Mais dois comportamentos que vale conhecer: realloc(NULL, n) se comporta exatamente como malloc(n), o que deixa uma função de crescimento lidar com sua primeira chamada sem um caso especial. E crescer deixa os novos bytes não inicializados - apenas o conteúdo antigo é preservado.
Aumentando um array: o padrão canônico
Aqui está o padrão que quase todo contêiner em C usa - um comprimento, uma capacidade e dobrar quando eles se encontram.
Dois pontos de projeto carregam o peso aqui.
Dobrar, não somar um. Crescer por uma quantidade fixa faz cada push copiar o array inteiro, então preencher n elementos custa mais ou menos n² cópias de bytes. Dobrar torna as cópias raras o bastante para que cada push custe tempo constante em média. O início cap == 0 ? 4 : cap * 2 trata o primeiro push sem um ramo separado, porque realloc(NULL, n) é simplesmente malloc.
A struct é dona de uma alocação. v.data é liberado exatamente uma vez, por quem quer que seja dono da struct. Escrever essa regra de propriedade - em um comentário, ou pareando todo vec_init com um vec_free - é o que impede um contêiner que cresce de vazar enquanto circula entre funções.
Encolhendo
realloc com um tamanho menor apara um bloco, o que é útil depois de você ter lido uma quantidade desconhecida de dados em um buffer superdimensionado:
Um encolhimento que falha não é um erro que mereça abortar: o bloco antigo, maior, continua válido e continua correto. Este é o único caso em que ignorar a falha é a decisão certa - mas note que ainda passa por tmp, porque a regra de não sobrescrever o ponteiro vivo não muda.
Qual deles usar
| Você quer | Use |
|---|---|
| Bytes, conteúdo irrelevante | malloc(n * sizeof *p) |
| Uma contagem de itens, limpos com zero | calloc(n, sizeof *p) |
| Uma contagem vinda de entrada não confiável | calloc, pela checagem de overflow |
| O bloco que você tem, maior ou menor | realloc através de um ponteiro tmp |
| Primeira alocação em um laço de crescimento | realloc(NULL, n) - sem caso especial |
Todos os quatro são liberados pelo mesmo free, exatamente uma vez.
Erros comuns
p = realloc(p, n)- vaza em caso de falha. Sempre passe portmp.- Guardar um ponteiro antigo para dentro do bloco. Depois do
reallocos dados podem ter se movido, então todo ponteiro interno salvo ou endereço derivado de índice está obsoleto. Índices sobrevivem a uma mudança de lugar; ponteiros não. - Supor que os novos bytes são zero. Apenas
calloczera.reallocdeixa a região de crescimento não inicializada. calloc(n * size, 1)- isso joga fora a checagem de overflow para a qual a forma de dois argumentos existe.- Liberar o ponteiro antigo depois de um
reallocbem-sucedido.reallocjá o liberou; um segundofreeé um double free.
Perguntas frequentes
Qual é a diferença entre calloc e malloc em C?
Duas diferenças. calloc(n, size) recebe a contagem e o tamanho do elemento separadamente e verifica que multiplicá-los não estoura, enquanto malloc(n * size) pode dar a volta silenciosamente. E calloc preenche o bloco com zeros, ao passo que malloc o deixa com o que quer que estivesse ali antes.
Como funciona o realloc em C?
realloc(p, newsize) devolve um bloco do novo tamanho com o conteúdo existente preservado até o menor dos dois tamanhos. Ele pode aumentar o bloco no lugar ou movê-lo, então o ponteiro retornado é o único que você pode continuar usando - o antigo pode já ter sido liberado.
Por que atribuir realloc a um ponteiro temporário?
Porque p = realloc(p, n) sobrescreve p com NULL quando a chamada falha, e o bloco original continua alocado sem nada apontando para ele - um vazamento. Atribua a um tmp primeiro, teste se é NULL e só então escreva p = tmp.
Quando devo usar calloc em vez de malloc?
Quando você genuinamente precisa da memória zerada (um array de contadores, uma struct cujos campos devem começar vazios, um buffer que você vai preencher parcialmente) ou quando o tamanho é uma contagem vezes um tamanho de elemento que pode ser grande. Se você vai sobrescrever cada byte de qualquer jeito, malloc poupa o trabalho de zerar.