Todo array que você escreveu até aqui tinha o tamanho fixado em tempo de compilação: int notas[10]; reserva dez posições, precise você de três ou de trezentas. Isso funciona até o tamanho só ser conhecido enquanto o programa roda - quantas linhas um arquivo tem, quantos registros o usuário digita. A memória dinâmica resolve isso: você pede os bytes de que precisa, quando precisa, e os devolve quando termina.
As ferramentas estão em <stdlib.h>:
#include <stdlib.h>
Por que o heap existe
C dá ao seu programa duas regiões principais de memória. Variáveis locais vivem no armazenamento automático (a pilha): o compilador decide o tamanho delas, e elas somem no momento em que a função retorna. Alocações dinâmicas vivem no heap: você decide o tamanho em tempo de execução, e o bloco continua vivo até você liberá-lo explicitamente.
Essa segunda propriedade é a razão de verdade para usar malloc. Uma função pode alocar um bloco, devolver o ponteiro, e a memória continua válida em quem chamou - algo que um array local nunca consegue. As duas regiões são comparadas em detalhe em pilha vs heap.
malloc: pedindo bytes
malloc recebe uma contagem de bytes e devolve um void * apontando para essa quantidade de memória não inicializada, ou NULL se o pedido falhar.
Quatro coisas nesse programa curto são toda a disciplina:
- A expressão de tamanho é
n * sizeof *notas. Leia como "n de seja lá o quenotasaponta". - O resultado é verificado contra
NULLantes de qualquer uso. - O bloco é usado exatamente como um array -
notas[i]funciona porque indexação é aritmética de ponteiros. - Ele é liberado uma vez, e o ponteiro é então definido como
NULL.
Note que não há conversão no valor de retorno. Em C, void * converte para qualquer ponteiro de objeto automaticamente, e escrever (int *)malloc(...) acrescenta ruído e ainda esconde um <stdlib.h> faltando. (C++ exige a conversão; C não.)
O idioma do sizeof
Por que sizeof *notas em vez de sizeof(int)? Porque ele não fica desatualizado.
int *a = malloc(n * sizeof *a); /* n ints, seja la o que int for aqui */
long *b = malloc(n * sizeof *b); /* mesma forma de linha, tamanho certo */
/* a forma fragil */
long *c = malloc(n * sizeof(int)); /* compila, aloca pouco demais */
A última linha é um bug real sobre o qual nenhum compilador avisa: c é um long *, a alocação foi dimensionada para int, e toda escrita além da primeira metade passa do fim. Com sizeof *c o tamanho acompanha a declaração automaticamente.
sizeof *p não desreferencia p - sizeof é avaliado em tempo de compilação apenas a partir do tipo, então é seguro mesmo quando p está não inicializado ou é NULL.
Memória não inicializada
malloc não limpa o que entrega. Os bytes guardam o que quer que estivesse lá antes.
Quando você quer o bloco zerado, calloc faz isso em um passo - veja calloc e realloc.
free: devolvendo
free(p) devolve o bloco ao alocador. Três regras:
- Passe o ponteiro exato que o
mallocdevolveu, não um que foi avançado.free(p + 1)é comportamento indefinido. - Libere cada bloco exatamente uma vez. Duas vezes é um double free, que corrompe a contabilidade interna do alocador.
- Depois de liberar, o ponteiro está velho. Usá-lo é um use-after-free.
int *p = malloc(sizeof *p);
free(p);
*p = 5; /* use-after-free - comportamento indefinido */
free(p); /* double free - comportamento indefinido */
Nenhuma das duas linhas é obrigada a travar imediatamente, que é o que as torna perigosas: o programa pode rodar por minutos e então falhar em algum lugar sem relação. A defesa barata é zerar o ponteiro:
free(p);
p = NULL;
free(NULL) é explicitamente definido como não fazendo nada, então um segundo free(p) depois disso é inofensivo, e *p vira uma falha imediata no ponto real do erro, em vez de corrupção silenciosa.
Um array dinâmico, do começo ao fim
Aqui está a forma de uma alocação de verdade: ler uma contagem em tempo de execução, dimensionar o bloco a partir dela, usar e liberar.
A conversão para size_t na contagem importa quando os tamanhos crescem: n * sizeof *valores com um n int comum pode transbordar antes de a multiplicação sequer chegar ao malloc, produzindo um bloco bem menor que o pretendido. Multiplicar em size_t evita isso.
Alocar dentro de uma função
A regra de tempo de vida do heap é o que torna isto legal - e o que torna ilegal devolver um array local:
len + 1 deixa espaço para o terminador nulo - a mesma regra de dimensionamento de sempre em strings. E note o comentário acima da função: quando uma função devolve memória alocada, quem a libera faz parte do contrato dela. Escrever isso não é burocracia; é a única coisa que impede um vazamento.
Structs no heap
O mesmo idioma funciona para qualquer tipo:
Note que o segundo ramo de falha libera p antes de retornar. Assim que uma função guarda mais de uma alocação, todo caminho de erro precisa liberar as que já foram feitas - a fonte mais comum dos vazamentos descritos em vazamentos de memória.
Checklist
- Sempre verifique se o
mallocdevolveuNULLantes do primeiro uso. - Dimensione com
n * sizeof *p, multiplicando emsize_t. - Sem conversão no valor de retorno em C.
- Memória de
mallocnão é inicializada; escreva antes de ler. - Libere exatamente uma vez, com o ponteiro original, e então defina-o como
NULL. - Todo caminho de erro depois de uma alocação precisa liberar o que já foi alocado.
Perguntas frequentes
O que o malloc faz em C?
malloc(n) pede n bytes de memória ao heap e devolve um ponteiro para o início desse bloco, ou NULL se o pedido não puder ser atendido. O bloco vive até você passar esse mesmo ponteiro para free - diferente de uma variável local, ele não é liberado quando a função retorna.
Como se usa malloc e free em C?
Aloque com int *p = malloc(n * sizeof *p);, verifique if (p == NULL) antes de tocar nele, use como um array e então free(p); exatamente uma vez quando terminar. Definir p = NULL depois transforma um uso acidental posterior em uma falha limpa, em vez de corrupção silenciosa.
Por que malloc(n * sizeof *p) é melhor que malloc(n * sizeof(int))?
Porque sizeof *p acompanha o tipo do ponteiro automaticamente. Se p depois virar um long * ou um struct Point *, o tamanho da alocação se atualiza sozinho; escrever sizeof(int) deixa um tamanho que passou a estar errado e que o compilador não vai apontar.
O que acontece se você não liberar memória em C?
O bloco fica alocado por toda a vida do processo - um vazamento de memória. Um programa curto escapa disso porque o sistema operacional recupera tudo na saída, mas um programa de longa duração vaza um pouco a cada passagem e acaba esgotando a memória. Veja vazamentos de memória.