Menu

malloc em C: alocação dinâmica de memória, free e sizeof

Por que o heap existe, como o malloc entrega memória cujo tamanho é decidido em tempo de execução, o idioma p = malloc(n * sizeof *p), verificar NULL, liberar exatamente uma vez e um array dinâmico do começo ao fim.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

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:

  1. A expressão de tamanho é n * sizeof *notas. Leia como "n de seja lá o que notas aponta".
  2. O resultado é verificado contra NULL antes de qualquer uso.
  3. O bloco é usado exatamente como um array - notas[i] funciona porque indexação é aritmética de ponteiros.
  4. 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 malloc devolveu, 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 malloc devolveu NULL antes do primeiro uso.
  • Dimensione com n * sizeof *p, multiplicando em size_t.
  • Sem conversão no valor de retorno em C.
  • Memória de malloc nã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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR