C malloc e free
Na aula Pilha e Heap você viu que o heap é a parte da memória que o programa pede durante a execução, do tamanho que quiser, e que vive até ser devolvida. As duas funções que fazem isso são o malloc (de memory allocation, "reserva de memória"), que pede um pedaço do heap, e o free ("liberar"), que o devolve. São duas funções pequenas, mas quase todo programa em C de verdade depende delas, e quase todos os erros graves de C nascem de usá-las errado. Nesta aula você aprende o uso certo e vê, com saídas reais, o que acontece em cada erro.
- A reservar memória com
malloce a calcular o tamanho comsizeof *p. - A criar um array do tamanho que o usuário pedir, durante a execução.
- Por que todo
mallocprecisa ser conferido contraNULL. - Que o
mallocnão limpa a memória: o que vem lá dentro é lixo. - A devolver a memória com
freee as três regras dofree. - Os erros clássicos (ponteiro pendurado,
freeduplo,freede um endereço errado), como o gcc, o AddressSanitizer e o Valgrind os mostram, e por que o editor do guia não os mostra.
Esta aula supõe que você já leu Ponteiros, Ponteiros e Arrays e Pilha e Heap. 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 guarda-volumes
Volte ao guarda-volumes do restaurante da aula Pilha e Heap. O atendimento funciona assim:
- Você vai ao balcão e diz quantos bytes precisa: "quero um armário de 40 bytes".
- O atendente procura um armário livre desse tamanho e te entrega o número dele (o endereço). Se não houver nenhum, ele te entrega um papel escrito "NENHUM" (o
NULL). - Você guarda o que quiser lá dentro, pelo tempo que quiser. O armário vem como o último cliente deixou: pode ter lixo dentro.
- Quando não precisa mais, você devolve a chave no balcão. Daí em diante o armário é de outra pessoa: abrir de novo é invasão, e devolver a mesma chave duas vezes deixa o atendente maluco.
| No guarda-volumes | Em C |
|---|---|
| Pedir um armário de N bytes | malloc(N) |
| O número do armário | O endereço que o malloc devolve, guardado num ponteiro |
| O papel "NENHUM" | NULL: não havia memória |
| O lixo do cliente anterior | O conteúdo indefinido da memória nova |
| Devolver a chave | free(ponteiro) |
1. O primeiro malloc: um int no heap
O programa mais simples possível: reservar espaço para um inteiro, usar e devolver.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *pontos = malloc(sizeof *pontos);
if (pontos == NULL) {
printf("Faltou memória!\n");
return 1;
}
*pontos = 1500;
printf("Reservei %zu bytes no heap.\n", sizeof *pontos);
printf("Pontos: %d\n", *pontos);
free(pontos);
return 0;
}
- #include <stdlib.h>
- O
malloce ofreemoram nastdlib.h("standard library", biblioteca padrão), não nastdio.h. Esquecer este include é um erro comum (veja "Quando dá errado"). - int *pontos = malloc(sizeof *pontos);
- O coração da aula. O
mallocrecebe quantos bytes você quer e devolve o endereço do primeiro deles.sizeof *pontosquer dizer "o tamanho daquilo para ondepontosaponta", ou seja, o tamanho de umint: 4 bytes. O endereço vai para o ponteiropontos. - if (pontos == NULL) { … }
- Se não houver memória, o
mallocdevolveNULL(aula Ponteiro Nulo). Usar umNULLderruba o programa, então a conferência vem logo depois de todomalloc. - *pontos = 1500;
- Daqui em diante,
*pontosé umintcomo outro qualquer: dá para guardar, ler e fazer contas. A diferença é onde ele mora: no heap, e não na pilha. - free(pontos);
- Devolve os 4 bytes. Repare que se passa o ponteiro (o número do armário), sem o
*. Depois dofree,*pontosnão pode mais ser usado.
$ gcc -std=c23 -Wall -Wextra um.c -o um $ ./um Reservei 4 bytes no heap. Pontos: 1500
De onde vem o tipo do ponteiro?
O malloc não sabe o que você vai guardar: ele só entrega bytes. Por isso devolve um void *, o "endereço sem etiqueta" da aula Ponteiro void*. Em C, um void * vira qualquer outro ponteiro sozinho, na atribuição: é o int *pontos = … que diz "aqui moram inteiros".
Você vai encontrar na internet int *p = (int *)malloc(sizeof(int));. O (int *) é uma conversão (aula Conversão de Tipos) que em C não é necessária: o void * já se converte sozinho. Ela é obrigatória em C++, e por isso aparece em códigos escritos por quem vem de lá. Em C, deixe sem: é menos para escrever e menos para errar.
Missão: o programa ainda não reserva nada: vidas começa com NULL e ele mostra Faltou memória!. Troque o NULL por um malloc do tamanho certo e acrescente o free(vidas); antes do return 0;. A saída deve ser Vidas: 3.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *vidas = NULL;
if (vidas == NULL) {
printf("Faltou memória!\n");
return 1;
}
*vidas = 3;
printf("Vidas: %d\n", *vidas);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *vidas = malloc(sizeof *vidas);
if (vidas == NULL) {
printf("Faltou memória!\n");
return 1;
}
*vidas = 3;
printf("Vidas: %d\n", *vidas);
free(vidas);
return 0;
}
2. Quantos bytes pedir: sizeof
O malloc conta em bytes, nunca em "itens". Para guardar 10 inteiros, você precisa de 10 vezes o tamanho de um inteiro. Há duas formas de escrever isso:
| Forma | Exemplo | O que acontece se o tipo mudar |
|---|---|---|
sizeof *p (o tamanho daquilo que o ponteiro aponta) | int *idades = malloc(10 * sizeof *idades); | Se um dia idades virar long *, o tamanho se ajusta sozinho. |
sizeof(tipo) | int *idades = malloc(10 * sizeof(int)); | Se idades virar long * e alguém esquecer de mudar o sizeof(int), o bloco fica pequeno demais. |
As duas funcionam, e você vai ver as duas por aí. Este guia prefere sizeof *p: o tipo é escrito uma vez só. Repare que o sizeof *idades não segue o ponteiro de verdade (ele ainda nem tem valor); o compilador só olha o tipo de *idades. Mais sobre o operador em sizeof.
O erro mais comum é esquecer a multiplicação: malloc(n) reserva n bytes, não n inteiros. A Ana queria 10 idades:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 10;
int *idades = malloc(n);
if (idades == NULL) {
return 1;
}
for (int i = 0; i < n; i++) {
idades[i] = 20 + i;
}
printf("Última idade: %d\n", idades[n - 1]);
free(idades);
return 0;
}
$ gcc -std=c23 -Wall -Wextra semsizeof.c -o semsizeof « nenhum aviso $ ./semsizeof malloc(): corrupted top size Abortado (imagem do núcleo gravada) ./semsizeof
O programa pediu 10 bytes e escreveu 40 (10 inteiros de 4 bytes). Os 30 bytes a mais caíram em cima das anotações internas que o malloc guarda ao lado de cada bloco. Quando o printf pediu memória para ele mesmo, o malloc encontrou as anotações estragadas ("tamanho do topo corrompido"), desistiu e abortou o programa (código 134). Repare que a mensagem não fala da linha 11, onde está o erro: ela aparece mais tarde, em outro lugar. Com o AddressSanitizer (aula Sanitizers), a linha certa aparece:
$ gcc -std=c23 -Wall -Wextra -g -fsanitize=address semsizeof.c -o semsizeof
$ ./semsizeof
=================================================================
==419872==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7b4eff9e0018 at pc 0x5602f1e2e2da bp 0x7ffc3f3235a0 sp 0x7ffc3f323590
WRITE of size 4 at 0x7b4eff9e0018 thread T0
#0 0x5602f1e2e2d9 in main /home/ana/c/semsizeof.c:11
«…»
0x7b4eff9e001a is located 0 bytes after 10-byte region [0x7b4eff9e0010,0x7b4eff9e001a)
allocated by thread T0 here:
#0 0x7f2f00b2b60f in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:67
#1 0x5602f1e2e268 in main /home/ana/c/semsizeof.c:6
«… o resto do relatório …»
heap-buffer-overflow é "estouro de um bloco do heap": uma escrita de 4 bytes (WRITE of size 4, um int) na linha 11, logo depois do fim de uma região de 10 bytes (10-byte region) reservada na linha 6. O "10 bytes" é a pista: era para ser 40. O conserto é malloc(n * sizeof *idades).
O mesmo semsizeof.c no editor mostra Última idade: 29 e termina com código 0, como se estivesse certo. O malloc do editor (da wasi-libc, para WebAssembly) arruma a memória de outro jeito e não confere as anotações. O erro continua lá: só não aparece. Por isso, os programas com malloc merecem uma rodada com -fsanitize=address ou com o Valgrind num Linux de verdade.
3. Um array do tamanho que o usuário pedir
Este é o motivo número um para usar o malloc: o tamanho só é conhecido durante a execução. A professora Ana não sabe quantos alunos cada turma tem; o programa pergunta e reserva exatamente o necessário:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n;
printf("Quantos alunos? ");
if (scanf("%d", &n) != 1 || n <= 0) {
printf("Número inválido.\n");
return 1;
}
double *notas = malloc(n * sizeof *notas);
if (notas == NULL) {
printf("Faltou memória!\n");
return 1;
}
for (int i = 0; i < n; i++) {
printf("Nota %d: ", i + 1);
if (scanf("%lf", ¬as[i]) != 1) {
printf("Nota inválida.\n");
free(notas);
return 1;
}
}
double soma = 0;
for (int i = 0; i < n; i++) {
soma += notas[i];
}
printf("Média da turma: %.2f\n", soma / n);
free(notas);
return 0;
}
- if (scanf("%d", &n) != 1 || n <= 0)
- Antes de reservar, confira o número: se o usuário digitar
quatroou-3, o programa para aqui. Umnnegativo viraria um tamanho gigante nomalloc, que recebe umsize_t(sem sinal). - double *notas = malloc(n * sizeof *notas);
nnotas de 8 bytes cada (o tamanho de umdouble). Com 4 alunos, 32 bytes.- ¬as[i]
- O ponteiro é usado com colchetes, como um array comum (aula Ponteiros e Arrays):
notas[i]é a notai, e¬as[i]é o endereço dela, que oscanfprecisa. - free(notas); return 1;
- Se uma nota vier errada, o programa desiste. Mas antes devolve a memória: todo caminho que sai do programa depois do
mallocpassa por umfree. (Na saída domaino sistema recolheria tudo de qualquer jeito, mas o hábito importa: dentro de uma função comum, esquecer aqui é um vazamento.) - free(notas);
- Um único
freedevolve o bloco inteiro, asnnotas de uma vez. Nunca se libera item por item.
$ gcc -std=c23 -Wall -Wextra notas.c -o notas $ ./notas Quantos alunos? 4 Nota 1: 7.5 Nota 2: 8 Nota 3: 6.5 Nota 4: 9 Média da turma: 7.75 $ ./notas Quantos alunos? quatro Número inválido.
Compare com um array comum, double notas[30];: ou sobra espaço (turma de 12), ou falta (turma de 45). Com o malloc, o tamanho é exato. E se o tamanho mudar depois que o array já existe (chegou um aluno novo), o realloc da próxima aula, calloc e realloc, aumenta o bloco.
Missão: este programa usa um array fixo de 3 notas, e a turma de hoje tem 5 alunos (a entrada já está pronta na caixa). Rode e veja Só cabem 3 notas!. Troque o array por um malloc de n notas, logo depois de ler o n (apague o teste do "só cabem 3"), confira o NULL e chame free no fim. A saída deve terminar com Média da turma: 8.10.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
double notas[3];
int n;
printf("Quantos alunos? ");
if (scanf("%d", &n) != 1 || n <= 0) {
printf("Número inválido.\n");
return 1;
}
if (n > 3) {
printf("Só cabem 3 notas!\n");
return 1;
}
for (int i = 0; i < n; i++) {
printf("Nota %d: ", i + 1);
if (scanf("%lf", ¬as[i]) != 1) {
printf("Nota inválida.\n");
return 1;
}
}
double soma = 0;
for (int i = 0; i < n; i++) {
soma += notas[i];
}
printf("Média da turma: %.2f\n", soma / n);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n;
printf("Quantos alunos? ");
if (scanf("%d", &n) != 1 || n <= 0) {
printf("Número inválido.\n");
return 1;
}
double *notas = malloc(n * sizeof *notas);
if (notas == NULL) {
printf("Faltou memória!\n");
return 1;
}
for (int i = 0; i < n; i++) {
printf("Nota %d: ", i + 1);
if (scanf("%lf", ¬as[i]) != 1) {
printf("Nota inválida.\n");
free(notas);
return 1;
}
}
double soma = 0;
for (int i = 0; i < n; i++) {
soma += notas[i];
}
printf("Média da turma: %.2f\n", soma / n);
free(notas);
return 0;
}
4. Conferir o NULL: quando o malloc falha
O heap é grande, mas não é infinito. Quando não dá para atender o pedido, o malloc não trava nem avisa: ele devolve NULL, em silêncio. A Ana pediu 100 bilhões de bytes (100 GB) num computador que não tem tudo isso:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t bytes = 100000000000;
char *bloco = malloc(bytes);
if (bloco == NULL) {
printf("Não deu: malloc devolveu NULL.\n");
return 1;
}
printf("Consegui!\n");
free(bloco);
return 0;
}
$ gcc -std=c23 -Wall -Wextra enorme.c -o enorme $ ./enorme Não deu: malloc devolveu NULL.
Sem o if, a primeira escrita em bloco[0] seria uma escrita no endereço zero: Falha de segmentação (aula Ponteiro Nulo). Com o if, o programa sai de forma organizada, com uma mensagem que diz o que houve. Num programa pequeno de aula, o malloc quase nunca falha; mas o hábito de conferir custa uma linha e evita um erro que, quando acontece, é difícil de reproduzir.
O programa do editor roda em WebAssembly de 32 bits, com no máximo 256 MB de memória. Lá, um malloc de 300 MB já devolve NULL, e o mesmo pedido funciona no gcc do Linux. Outro detalhe: no editor, size_t tem 4 bytes e não consegue guardar 100 bilhões; o Clang avisa implicit conversion from 'long long' to 'size_t' (aka 'unsigned long') changes value from 100000000000 to 1215752192 [-Wconstant-conversion] e pede uns 1,2 GB, que também voltam como NULL.
5. O malloc não limpa a memória
O armário vem como o último cliente deixou. O malloc só marca o bloco como seu; não apaga o que estava lá. Este programa reserva 8 inteiros, preenche, devolve, e depois reserva outros 8 sem preencher, mostrando o que veio dentro:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *antes = malloc(8 * sizeof *antes);
if (antes == NULL) {
return 1;
}
for (int i = 0; i < 8; i++) {
antes[i] = (i + 1) * 11;
}
free(antes);
int *notas = malloc(8 * sizeof *notas);
if (notas == NULL) {
return 1;
}
for (int i = 0; i < 8; i++) {
printf("%d ", notas[i]);
}
printf("\n");
free(notas);
return 0;
}
$ gcc -std=c23 -Wall -Wextra lixo.c -o lixo $ ./lixo 1694505054 5 0 0 55 66 77 88 $ ./lixo 1575119293 5 0 0 55 66 77 88 « o primeiro número muda a cada execução
O segundo malloc recebeu o mesmo armário que o primeiro acabou de devolver. As quatro últimas posições ainda têm o 55 66 77 88 do programa anterior. As quatro primeiras foram rabiscadas pelo próprio malloc, que usa o começo de um bloco livre para anotar a lista de blocos livres. No editor do guia, o resultado é outro (11 22 33 44 55 66 77 88, porque o malloc da wasi-libc anota em outro lugar), e isso só confirma a regra: o conteúdo de um bloco novo é lixo, e lixo pode ser qualquer coisa.
Por isso, depois de um malloc, dê valor a tudo antes de ler (como o laço do scanf do notas.c). Se você quer o bloco começando com zeros, há uma função própria, o calloc, da próxima aula, calloc e realloc.
6. free: devolver a chave
O free recebe o endereço que o malloc deu e devolve o bloco ao heap, para ser reaproveitado. Ele não devolve nada (é void) e não muda o seu ponteiro: depois do free(p), p continua guardando o mesmo número, só que agora de um armário que não é mais seu. As regras são três:
| Regra | Se quebrar |
|---|---|
1. Depois do free(p), não use mais *p nem p[i]. | "Ponteiro pendurado": lê lixo ou estraga dados de outro bloco. |
| 2. Cada bloco é liberado uma vez só. | free(): double free detected, o programa aborta. |
3. Só se passa ao free um endereço que veio do malloc (ou do calloc/realloc), e exatamente o endereço do começo. | free(): invalid pointer, o programa aborta. |
E uma exceção útil: free(NULL) é permitido e não faz nada. Por isso o costume de, logo depois do free(p), escrever p = NULL;: um segundo free(p) por engano vira inofensivo, e um if (p != NULL) passa a dizer a verdade.
Regra 1: o ponteiro pendurado
Um ponteiro que guarda o endereço de memória já liberada se chama ponteiro pendurado (dangling pointer). O p = NULL protege aquele ponteiro, mas não as cópias dele. Aqui o copia continua pendurado:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *placar = malloc(sizeof *placar);
if (placar == NULL) {
return 1;
}
*placar = 7;
int *copia = placar;
free(placar);
placar = NULL;
printf("Placar: %d\n", *copia);
return 0;
}
$ gcc -std=c23 -Wall -Wextra apelido.c -o apelido
apelido.c: In function ‘main’:
apelido.c:15:5: warning: pointer ‘copia’ used after ‘free’ [-Wuse-after-free]
15 | printf("Placar: %d\n", *copia);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
apelido.c:12:5: note: call to ‘free’ here
12 | free(placar);
| ^~~~~~~~~~~~
$ ./apelido
Placar: 1495898798
- warning: pointer ‘copia’ used after ‘free’ [-Wuse-after-free]
- "Ponteiro
copiausado depois dofree". O gcc percebeu quecopiaeplacarapontam para o mesmo lugar, e anotemostra ofreeda linha 12. Ele só consegue avisar porque tudo está na mesma função; quando ofreefica em outra função, o aviso some (veja a regra 2). - Placar: 1495898798
- O programa rodou e mostrou lixo em vez de 7: o
freeusou o bloco para as anotações dele. Nada de falha de segmentação: o erro passa sem barulho, e o programa segue com um valor errado.
Para ver o erro de verdade, chame os fiscais. O AddressSanitizer para o programa na linha do erro e conta a história do bloco:
$ gcc -std=c23 -Wall -Wextra -g -fsanitize=address apelido.c -o apelido
«… o mesmo aviso …»
$ ./apelido
=================================================================
==419821==ERROR: AddressSanitizer: heap-use-after-free on address 0x7bcc115e0010 at pc 0x55ef43f57308 bp 0x7fff91a04070 sp 0x7fff91a04060
READ of size 4 at 0x7bcc115e0010 thread T0
#0 0x55ef43f57307 in main /home/ana/c/apelido.c:15
«…»
0x7bcc115e0010 is located 0 bytes inside of 4-byte region [0x7bcc115e0010,0x7bcc115e0014)
freed by thread T0 here:
#0 0x7fac1272a3ff in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:51
#1 0x55ef43f572c8 in main /home/ana/c/apelido.c:12
«…»
previously allocated by thread T0 here:
#0 0x7fac1272b60f in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:67
#1 0x55ef43f5725e in main /home/ana/c/apelido.c:5
«… o resto do relatório …»
Leia de cima para baixo: leitura de 4 bytes depois do free (heap-use-after-free) na linha 15; o bloco foi liberado na linha 12 e tinha sido reservado na linha 5. O Valgrind, sem recompilar com nada especial (só o -g), diz o mesmo com outras palavras:
$ gcc -std=c23 -Wall -Wextra -g apelido.c -o apelido-vg «… o mesmo aviso …» $ valgrind ./apelido-vg «… o cabeçalho …» ==419837== Invalid read of size 4 ==419837== at 0x40011DB: main (apelido.c:15) ==419837== Address 0x4a8e040 is 0 bytes inside a block of size 4 free'd ==419837== at 0x48538BF: free (vg_replace_malloc.c:990) ==419837== by 0x40011CE: main (apelido.c:12) ==419837== Block was alloc'd at ==419837== at 0x4850858: malloc (vg_replace_malloc.c:447) ==419837== by 0x400119E: main (apelido.c:5) ==419837== Placar: 7 «… o resumo: ERROR SUMMARY: 1 errors from 1 contexts …»
Dentro do Valgrind o programa até mostrou Placar: 7, porque o Valgrind não reaproveita o bloco liberado logo de cara. O valor certo, aqui, não quer dizer que o código está certo: o Invalid read diz que não está.
Placar: 7, sem erro nenhum
O editor mostra Placar: 7 e código 0. O Clang não tem o aviso -Wuse-after-free do gcc, e o malloc do editor não rabisca o bloco liberado. É o pior cenário: o erro existe e tudo parece certo. Os erros de memória que não são ponteiro nulo não aparecem no editor; para eles, use o gcc com -fsanitize=address ou o Valgrind no seu Linux.
Missão: rode e veja o editor mostrar Placar depois do free: 7, um valor que já não existe (no gcc sairia lixo). Conserte com o costume desta seção: logo depois do free(placar);, faça o ponteiro valer NULL. Aí o if passa a dizer a verdade, e a saída mostra O placar já foi liberado.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *placar = malloc(sizeof *placar);
if (placar == NULL) {
return 1;
}
*placar = 7;
printf("Placar: %d\n", *placar);
free(placar);
if (placar != NULL) {
printf("Placar depois do free: %d\n", *placar);
} else {
printf("O placar já foi liberado.\n");
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *placar = malloc(sizeof *placar);
if (placar == NULL) {
return 1;
}
*placar = 7;
printf("Placar: %d\n", *placar);
free(placar);
placar = NULL;
if (placar != NULL) {
printf("Placar depois do free: %d\n", *placar);
} else {
printf("O placar já foi liberado.\n");
}
return 0;
}
Regra 2: o free duplo
O caso típico: uma função libera o bloco e quem a chamou não sabe, e libera de novo.
#include <stdio.h>
#include <stdlib.h>
void encerrar(int *placar) {
printf("Placar final: %d\n", *placar);
free(placar);
}
int main(void) {
int *placar = malloc(sizeof *placar);
if (placar == NULL) {
return 1;
}
*placar = 42;
encerrar(placar);
free(placar);
printf("Fim do jogo.\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra duplo.c -o duplo « nenhum aviso: os dois free estão em funções diferentes $ ./duplo Placar final: 42 free(): double free detected in tcache 2 Abortado (imagem do núcleo gravada) ./duplo $ echo $? 134
A própria glibc (a biblioteca C do Linux) percebeu: "free(): liberação dupla detectada" (o tcache é um cache interno do malloc) e abortou (134 = 128 + 6, o sinal SIGABRT). O Fim do jogo. nunca apareceu. Ela diz que houve, mas não onde. O ASan diz onde, e ainda mostra que o primeiro free foi dentro de encerrar:
$ gcc -std=c23 -Wall -Wextra -g -fsanitize=address duplo.c -o duplo
$ ./duplo
=================================================================
==419866==ERROR: AddressSanitizer: attempting double-free on 0x7b530fbe0010 in thread T0:
#0 0x7f3310d2a3ff in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:51
#1 0x5571f18f335b in main /home/ana/c/duplo.c:16
«…»
freed by thread T0 here:
#0 0x7f3310d2a3ff in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:51
#1 0x5571f18f32d7 in encerrar /home/ana/c/duplo.c:6
#2 0x5571f18f334f in main /home/ana/c/duplo.c:15
«…»
previously allocated by thread T0 here:
#0 0x7f3310d2b60f in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:67
#1 0x5571f18f32f0 in main /home/ana/c/duplo.c:10
«…»
SUMMARY: AddressSanitizer: double-free /home/ana/c/duplo.c:16 in main
==419866==ABORTING
No editor do guia, o free duplo passa calado: o programa mostra Placar final: 42 e Fim do jogo. e termina com 0. O conserto é decidir quem libera (seção 7): ou encerrar libera e o main não, ou o contrário. Nunca os dois.
Regra 3: só o endereço que veio do malloc
Dois jeitos de quebrar esta regra. O primeiro: liberar uma variável local, que mora na pilha e nunca veio do malloc:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int vidas = 3;
int *p = &vidas;
printf("Vidas: %d\n", *p);
free(p);
return 0;
}
$ gcc -std=c23 -Wall -Wextra freelocal.c -o freelocal
freelocal.c: In function ‘main’:
freelocal.c:8:5: warning: ‘free’ called on unallocated object ‘vidas’ [-Wfree-nonheap-object]
8 | free(p);
| ^~~~~~~
freelocal.c:5:9: note: declared here
5 | int vidas = 3;
| ^~~~~
$ ./freelocal
Vidas: 3
free(): invalid pointer
Abortado (imagem do núcleo gravada) ./freelocal
O gcc avisou ("free chamado num objeto que não foi reservado: vidas") e, ao rodar, a glibc abortou com "ponteiro inválido". O segundo jeito é mais traiçoeiro: o endereço veio do malloc, mas o ponteiro andou:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *notas = malloc(5 * sizeof *notas);
if (notas == NULL) {
return 1;
}
int *p = notas;
for (int i = 0; i < 5; i++) {
*p = 7;
p++;
}
printf("Preenchi 5 notas.\n");
free(p);
return 0;
}
$ gcc -std=c23 -Wall -Wextra freemeio.c -o freemeio « nenhum aviso $ ./freemeio Preenchi 5 notas. free(): invalid pointer Abortado (imagem do núcleo gravada) ./freemeio
Depois do laço, p aponta para depois da quinta nota, e não para o começo do bloco. O free precisa do endereço exato que o malloc devolveu, porque é ali que ele encontra as anotações do bloco. O conserto é free(notas);: guarde sempre o ponteiro original e ande com uma cópia. No editor do guia, os dois programas terminam com código 0, sem mensagem.
7. Quem libera? O dono da memória
A memória do heap não pertence a uma função: ela vive até alguém chamar free. Por isso, todo bloco precisa de um dono, a parte do programa responsável por liberá-lo. A regra mais usada:
- Uma função que reserva e devolve o ponteiro (como o
criar_contadorda aula Pilha e Heap) passa a posse para quem chamou: quem recebe o ponteiro dá ofree. - Uma função que só recebe o ponteiro para ler ou mudar o conteúdo (como um
mostrar(notas, n)) não libera nada. - Se uma função precisa liberar o que recebeu (como o
encerrardoduplo.c), o nome e um comentário devem dizer isso, e quem chamou não toca mais no ponteiro.
#include <stdio.h>
#include <stdlib.h>
/* Reserva n inteiros com os valores 1, 2, …, n.
Quem chamar é o dono: tem de chamar free no fim. */
int *sequencia(int n) {
int *v = malloc(n * sizeof *v);
if (v == NULL) {
return NULL;
}
for (int i = 0; i < n; i++) {
v[i] = i + 1;
}
return v;
}
/* Só lê: não libera. */
void mostrar(const int *v, int n) {
for (int i = 0; i < n; i++) {
printf("%d ", v[i]);
}
printf("\n");
}
int main(void) {
int *numeros = sequencia(5);
if (numeros == NULL) {
printf("Faltou memória!\n");
return 1;
}
mostrar(numeros, 5);
free(numeros);
return 0;
}
$ gcc -std=c23 -Wall -Wextra dono.c -o dono $ ./dono 1 2 3 4 5
Repare no const int *v de mostrar: além de não liberar, ela promete não mudar os números (aula const e Ponteiros). A biblioteca padrão segue a mesma regra: o strdup da aula Strings Dinâmicas devolve memória nova e a documentação dele diz "libere com free".
Qual usar
| Situação | Use |
|---|---|
| Poucos dados, tamanho fixo, usados só dentro da função | Variável ou array local, sem malloc (aula Pilha e Heap) |
| Tamanho conhecido só na execução, dados grandes, ou dados que saem da função | malloc + free |
| Bloco que precisa começar com zeros | calloc (próxima aula) |
| Bloco que precisa crescer ou encolher | realloc (próxima aula) e Array que Cresce |
O tamanho no malloc | n * sizeof *p (o tipo aparece uma vez só) |
(int *) na frente do malloc | Em C, não precisa. Só em C++. |
Quando dá errado
error: implicit declaration of function ‘malloc’
$ gcc -std=c23 -Wall -Wextra semstdlib.c -o semstdlib
semstdlib.c: In function ‘main’:
semstdlib.c:4:19: error: implicit declaration of function ‘malloc’ [-Wimplicit-function-declaration]
4 | int *pontos = malloc(sizeof *pontos);
| ^~~~~~
semstdlib.c:2:1: note: include ‘<stdlib.h>’ or provide a declaration of ‘malloc’
1 | #include <stdio.h>
+++ |+#include <stdlib.h>
2 |
«… o mesmo para o free …»
O que significa: "declaração implícita da função malloc": o programa usa malloc sem ter incluído o cabeçalho que a declara. A note já mostra o conserto, com um + na linha que falta. No editor, o Clang diz error: call to undeclared library function 'malloc' with type 'void *(__size_t)' (aka 'void *(unsigned long)'); ISO C99 and later do not support implicit function declarations e note: include the header <stdlib.h> or explicitly provide a declaration for 'malloc'.
Como consertar: #include <stdlib.h> no começo do arquivo.
malloc(): corrupted top size, free(): invalid next size e parecidos
O que significa: o programa escreveu além do fim de um bloco e estragou as anotações do malloc. A mensagem aparece mais tarde, num malloc ou free qualquer, longe do erro de verdade. A causa mais comum é o tamanho errado: malloc(n) em vez de malloc(n * sizeof *p), ou o + 1 do '\0' esquecido numa string.
Como consertar: confira as contas de cada malloc e rode com -g -fsanitize=address: o heap-buffer-overflow aponta a linha que escreveu demais e o tamanho do bloco.
free(): double free detected in tcache 2
O que significa: o mesmo bloco foi liberado duas vezes. No editor do guia, passa calado.
Como consertar: decida quem é o dono do bloco e libere num lugar só. Depois do free(p), faça p = NULL;. Para achar os dois free, use o ASan (attempting double-free) ou o Valgrind (Invalid free()).
free(): invalid pointer
O que significa: o free recebeu um endereço que não é o começo de um bloco do malloc: uma variável local (o gcc avisa com -Wfree-nonheap-object), um literal de texto ou um ponteiro que andou (p++). No editor do guia, passa calado.
Como consertar: passe ao free exatamente o ponteiro que o malloc devolveu. Para percorrer o bloco, use um índice (notas[i]) ou uma cópia do ponteiro.
O que significa: duas suspeitas: o programa lê um bloco do malloc antes de dar valor a ele (lixo), ou lê um bloco já liberado (ponteiro pendurado; o gcc às vezes avisa com -Wuse-after-free).
Como consertar: dê valor a cada posição antes de ler (ou use calloc), e não use ponteiros depois do free. O Valgrind acha os dois: Conditional jump or move depends on uninitialised value(s) para o lixo e Invalid read para o bloco liberado.
Falha de segmentação logo depois de um malloc
O que significa: o malloc devolveu NULL e o programa usou o ponteiro sem conferir.
Como consertar: um if (p == NULL) logo depois de cada malloc, com uma mensagem e uma saída organizada.
Referência rápida
| Código | O que faz |
|---|---|
#include <stdlib.h> | Declara malloc, free, calloc, realloc |
void *malloc(size_t bytes); | Reserva bytes bytes no heap; devolve o endereço do primeiro, ou NULL se não deu. O conteúdo é lixo. |
void free(void *p); | Devolve o bloco que começa em p. free(NULL) não faz nada. |
T *p = malloc(sizeof *p); | Um item do tipo T |
T *v = malloc(n * sizeof *v); | Um array de n itens do tipo T; use v[0] a v[n-1] |
if (p == NULL) { … } | Logo depois de todo malloc |
free(p); p = NULL; | Devolver e "esquecer" o endereço |
-fsanitize=address, valgrind | Acham estouro, uso depois do free, free duplo e vazamentos (no Linux; não no editor) |
Exercícios
1. O cabeçalho
Qual cabeçalho declara o malloc e o free?
2. Espaço para 20 preços
Complete para reservar 20 double:
3. Sem erro na falha
Quando não há memória, o que o malloc devolve?
4. Depois de int *v = malloc(5 * sizeof *v);, o que há em v[0] antes de qualquer atribuição?
5. Qual destas linhas é um erro?
6. Um programa com free duplo roda no editor do guia sem nenhuma mensagem. O que isso quer dizer?
Missão final: os preços da lojinha
Missão: a função ler_precos ainda não faz nada e devolve NULL. Escreva o corpo dela: reserve n preços com malloc, confira o NULL, leia cada preço com scanf("%lf", …) (se um falhar, libere o bloco e devolva NULL) e devolva o ponteiro. No main, que é o dono do bloco, devolva a memória com free(lista); no fim. A entrada já está na caixa; a saída deve terminar com:
Total: R$ 52.30 Mais caro: R$ 19.90
#include <stdio.h>
#include <stdlib.h>
/* Reserva e lê n preços. Quem chamar tem de chamar free. */
double *ler_precos(int n) {
/* escreva aqui */
return NULL;
}
int main(void) {
int n;
printf("Quantos produtos? ");
if (scanf("%d", &n) != 1 || n <= 0) {
printf("Número inválido.\n");
return 1;
}
double *lista = ler_precos(n);
if (lista == NULL) {
printf("Não consegui ler os preços.\n");
return 1;
}
double total = 0;
double maior = lista[0];
for (int i = 0; i < n; i++) {
total += lista[i];
if (lista[i] > maior) {
maior = lista[i];
}
}
printf("Total: R$ %.2f\n", total);
printf("Mais caro: R$ %.2f\n", maior);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
/* Reserva e lê n preços. Quem chamar tem de chamar free. */
double *ler_precos(int n) {
double *precos = malloc(n * sizeof *precos);
if (precos == NULL) {
return NULL;
}
for (int i = 0; i < n; i++) {
printf("Preço %d: ", i + 1);
if (scanf("%lf", &precos[i]) != 1) {
free(precos);
return NULL;
}
}
return precos;
}
int main(void) {
int n;
printf("Quantos produtos? ");
if (scanf("%d", &n) != 1 || n <= 0) {
printf("Número inválido.\n");
return 1;
}
double *lista = ler_precos(n);
if (lista == NULL) {
printf("Não consegui ler os preços.\n");
return 1;
}
double total = 0;
double maior = lista[0];
for (int i = 0; i < n; i++) {
total += lista[i];
if (lista[i] > maior) {
maior = lista[i];
}
}
printf("Total: R$ %.2f\n", total);
printf("Mais caro: R$ %.2f\n", maior);
free(lista);
return 0;
}
No seu Linux, rode a solução com valgrind --leak-check=full ./precos < entrada.txt e confira o All heap blocks were freed -- no leaks are possible. Apague o free(lista); e rode de novo: o Valgrind mostra o bloco perdido. É o assunto da aula Vazamentos de Memória.
Resumo
malloc(bytes)(destdlib.h) reserva memória no heap e devolve o endereço, ouNULLse não deu. Confira oNULLlogo depois de todomalloc.- O tamanho é em bytes:
T *v = malloc(n * sizeof *v);. Esquecer osizeofestoura o bloco, e o erro aparece longe (malloc(): corrupted top size). - O ponteiro do
mallocfunciona como um array:v[i]. E o tamanho pode vir do usuário. - O conteúdo de um bloco novo é lixo: dê valor antes de ler, ou use
calloc. free(p)devolve o bloco. Três regras: não usar depois, liberar uma vez só, e só o endereço exato que veio domalloc.free(NULL)não faz nada; façap = NULLdepois dofree.- Todo bloco tem um dono, responsável pelo
free. - No editor do guia, ponteiro pendurado,
freeduplo efreede endereço errado passam calados. No Linux, o gcc aborta em alguns casos, e o-fsanitize=addresse o Valgrind mostram todos, com a linha.