Pular para conteúdo

Semana 12 — Alocação dinâmica de memória (parte 1)

Nesta semana

Unidade 3 · Seg 26/10 · Qua 28/10 · Sex 30/10 · ⚠️ 28/10 (Dia do Servidor Público) pode afetar o encontro de quarta — ver plano de curso

Até agora, todo vetor que você declarou tinha um tamanho fixo, decidido por você enquanto escrevia o código (int v[100];). Mas e quando o tamanho só é conhecido durante a execução — quando é o usuário quem decide quantos elementos vai querer? Esta semana resolve esse problema com a alocação dinâmica de memória: pedir ao sistema, em tempo de execução, um bloco de memória do tamanho exato de que precisamos, usá-lo como um vetor e, ao terminar, devolvê-lo.

Este é o encontro natural de tudo o que você viu na Unidade 3: endereços, ponteiros e a divisão da memória em pilha e heap (Semana 10). Aqui os ponteiros deixam de ser um conceito abstrato e passam a fazer um trabalho concreto e indispensável. É um tópico difícil — leia com calma, desenhe a memória no papel e, principalmente, digite e execute cada exemplo.

🎯 Objetivos de aprendizagem

Ao final desta semana, você deverá ser capaz de:

  • explicar a diferença entre memória estática (tamanho fixo, decidido em compilação) e memória dinâmica (tamanho decidido em execução), relacionando-as com a pilha e o heap;
  • usar o operador sizeof para calcular corretamente o tamanho, em bytes, de um bloco a alocar;
  • alocar memória no heap com malloc, entendendo que ela devolve um ponteiro para o bloco reservado;
  • verificar sempre se a alocação teve sucesso (teste de NULL) antes de usar a memória;
  • usar a memória alocada exatamente como um vetor (v[i]);
  • liberar a memória com free quando ela não for mais necessária, e entender por que todo malloc precisa do seu free;
  • descrever, em linhas gerais, o que é um vazamento de memória e por que calloc pode ser conveniente.

📖 Segunda — Estudo do conteúdo

1. O problema: tamanho fixo × tamanho variável

Volte à ideia de vetor da Unidade 2. Para guardar as notas de uma turma, você escrevia algo assim:

int notas[100];   // reservo espaço para 100 notas

Isso funciona, mas esconde dois incômodos:

  1. E se a turma tiver mais de 100 alunos? O programa não tem para onde crescer — os índices notas[100], notas[101]... invadem memória que não é sua (comportamento indefinido, provável travamento).
  2. E se a turma tiver 5 alunos? Você reservou 100 posições e usa 5. As outras 95 ficaram desperdiçadas.

O ponto central é este: int notas[100]; fixa o tamanho em tempo de compilação — ou seja, o número 100 precisa ser conhecido enquanto você escreve o programa, muito antes de ele rodar. Mas, na vida real, quem conhece o tamanho é o usuário, e só em tempo de execução:

int n;
printf("Quantos alunos? ");
scanf("%d", &n);

int notas[n];   // <- e agora? n só foi conhecido AGORA, em execução

Esse int notas[n]; (chamado VLA, vetor de tamanho variável) existe em C moderno, mas tem limitações sérias: o vetor vai para a pilha (que é pequena e some quando a função termina), não pode ser redimensionado e nem toda situação o permite. A solução geral e profissional é outra: pedir a memória ao sistema, na quantidade certa, usando a alocação dinâmica.

1.1 Onde essa memória vive: pilha × heap

Na Semana 10 vimos que a memória de um programa em execução se organiza em regiões. Duas nos interessam agora:

Região O que guarda Tamanho decidido Tempo de vida Quem gerencia
Pilha (stack) variáveis locais e parâmetros; vetores de tamanho fixo em compilação some quando a função termina o compilador, automaticamente
Heap (monte) memória alocada dinamicamente em execução até você liberar com free você, manualmente
flowchart TB
    subgraph mem["Memória do programa em execução"]
        direction TB
        S["PILHA (stack)<br/>variaveis locais, parametros<br/>tamanho fixo, vida curta<br/>gerenciada pelo compilador"]
        H["HEAP (monte)<br/>blocos de malloc<br/>tamanho decidido em execucao<br/>voce libera com free"]
    end

A alocação dinâmica sempre acontece no heap. É isso que lhe dá dois superpoderes que a pilha não tem: o tamanho pode ser decidido em execução e o bloco sobrevive ao fim da função que o criou (contanto que você guarde o ponteiro para ele). Em troca, você assume uma responsabilidade: devolver a memória quando terminar.

Curiosidade 💡 — por que o heap "sobrevive" e a pilha não?

A pilha funciona como uma pilha de pratos: a cada chamada de função, empilha-se um "quadro" com suas variáveis locais; quando a função retorna, o quadro inteiro é desempilhado e aquele espaço é reaproveitado na hora. Por isso uma variável local não pode ser devolvida por endereço — o espaço dela já foi reciclado assim que a função acabou.

O heap é diferente: é uma área "livre" que o programa vai loteando sob demanda. Um bloco alocado com malloc fica lá até você mandar liberá-lo com free, mesmo que a função que o criou já tenha terminado. É exatamente por isso que uma função consegue alocar um vetor no heap e devolver um ponteiro para ele (veremos isso na sexta) — algo impossível com um vetor da pilha.

2. sizeof: medindo os tipos antes de alocar

Para pedir memória, precisamos dizer quantos bytes queremos. E, para isso, precisamos saber quanto ocupa cada tipo. O operador sizeof (que você já conheceu lá na Semana 01) responde exatamente a essa pergunta, devolvendo o tamanho em bytes de um tipo ou de uma variável:

printf("um int    ocupa %zu bytes\n", sizeof(int));    // tipicamente 4
printf("um double ocupa %zu bytes\n", sizeof(double));  // tipicamente 8
printf("um char   ocupa %zu bytes\n", sizeof(char));    // sempre 1

(Lembre: o especificador correto para o resultado de sizeof é %zu.)

Por que isso importa agora? Porque um vetor de n inteiros ocupa n * sizeof(int) bytes. Se cada int tem 4 bytes, um vetor de 10 inteiros ocupa 10 * 4 = 40 bytes. É essa conta que passaremos para o malloc.

Use sizeof, não o número "cru"

Você poderia escrever malloc(n * 4) chutando que int tem 4 bytes. Não faça isso. O tamanho de um int pode variar entre sistemas, e o dia em que você trocar o tipo (de int para double, por exemplo) esse 4 vira um bug silencioso. Escrever n * sizeof(int) é sempre correto e deixa clara a intenção. Regra de ouro: deixe o compilador calcular os tamanhos para você.

3. malloc: pedindo memória ao sistema

A função malloc (memory allocation) está na biblioteca <stdlib.h>. Ela faz uma coisa só, mas fundamental: reserva um bloco contíguo de memória no heap com o tamanho que você pediu e devolve um ponteiro para o início desse bloco.

#include <stdlib.h>   // malloc, free, calloc estao aqui

int *v = malloc(n * sizeof(int));

Vamos dissecar essa linha, que é o coração da semana:

  • n * sizeof(int) — o tamanho em bytes que queremos: espaço para n inteiros.
  • malloc(...) — pede esse bloco ao sistema e devolve o endereço do começo dele.
  • int *v = — guardamos esse endereço num ponteiro para int. A partir daqui, v aponta para o primeiro dos n inteiros reservados.
flowchart LR
    P["v (ponteiro na pilha)"] -->|aponta para| B
    subgraph heap["Bloco no HEAP: n * sizeof(int) bytes"]
        direction LR
        B["v[0]"] --- C["v[1]"] --- D["v[2]"] --- E["..."] --- F["v[n-1]"]
    end

Repare na divisão de trabalho: o ponteiro v é uma variável local pequena, que mora na pilha; o bloco de n inteiros que ele aponta mora no heap. O ponteiro é apenas o "endereço na etiqueta"; a memória de verdade está do outro lado da seta.

Curiosidade 💡 — void *: o ponteiro "genérico"

Tecnicamente, malloc devolve um void * — um ponteiro "sem tipo", que apenas diz "o bloco começa aqui" sem afirmar o que há dentro. Em C, um void * é convertido automaticamente para o tipo de ponteiro do lado esquerdo (int *, double *, etc.), então a atribuição int *v = malloc(...) simplesmente funciona. Faz sentido: malloc só sabe quantos bytes você pediu, não se são inteiros, reais ou caracteres — quem dá esse significado aos bytes é você, ao guardar o retorno num ponteiro de um tipo específico.

Fazer cast do malloc? Em C, prefira não fazer

Você verá, em muitos códigos antigos, o retorno do malloc sendo convertido explicitamente:

int *v = (int *) malloc(n * sizeof(int));   // cast desnecessario em C
Em C, esse (int *) é desnecessário (a conversão de void * é automática) e pode até mascarar um erro — o de esquecer o #include <stdlib.h>. Por isso, o estilo idiomático em C é escrever sem o cast:
int *v = malloc(n * sizeof(int));           // estilo C recomendado
(O cast é obrigatório em C++, o que costuma ser a origem da confusão. Na nossa disciplina, que é C, escreva sem cast.)

4. Sempre verifique se deu certo: o teste de NULL

O sistema tem uma quantidade finita de memória. Se você pedir mais do que há disponível, malloc não consegue atender e, em vez de um endereço válido, devolve o ponteiro especial NULL (o "endereço nenhum", que vale 0). Usar um ponteiro NULL como se fosse memória de verdade trava o programa (o famoso segmentation fault).

Por isso, toda chamada de malloc deve ser seguida de uma verificação:

int *v = malloc(n * sizeof(int));
if (v == NULL) {                       // a alocacao falhou?
    printf("Erro: memoria insuficiente.\n");
    return 1;                           // encerra sinalizando erro ao sistema
}
// daqui para baixo, v aponta para memoria valida com seguranca

Nunca confie que o malloc funcionou — teste NULL sempre

Pular o teste de NULL é um dos erros mais perigosos com alocação dinâmica, porque o programa parece funcionar... até o dia em que a memória acaba (um n gigante, um laço que aloca demais) e ele trava de forma inexplicável para quem não testou. Adote o hábito: malloc seguido, na linha de baixo, do if (p == NULL). Sempre.

Curiosidade 💡 — quando o malloc realmente falha?

Em programas pequenos de disciplina, malloc quase nunca falha — há memória de sobra. Mas a verificação não é "frescura": em sistemas reais (servidores, dispositivos embarcados, programas que processam arquivos enormes) a memória acaba de verdade, e um programa robusto precisa reagir com elegância em vez de simplesmente morrer. Aprender a testar NULL desde já é aprender a escrever código que não quebra na hora errada.

5. Usando a memória: é um vetor como qualquer outro

Aqui vem a parte tranquilizadora. Depois de alocado e verificado, o bloco se comporta exatamente como um vetor comum. Você usa a mesma notação de colchetes:

for (int i = 0; i < n; i++) {
    v[i] = i * i;              // escreve como em qualquer vetor
}
printf("%d\n", v[0]);         // le como em qualquer vetor

Isso não é coincidência: em C, o nome de um vetor é, essencialmente, um ponteiro para o seu primeiro elemento, e v[i] é apenas uma forma legível de dizer "o elemento a i posições depois de onde v aponta". Ou seja, um vetor estático e um bloco de malloc são, para efeito de uso, a mesma coisa — a diferença está em onde vivem (pilha × heap) e em quem os cria e destrói.

6. free: devolvendo a memória

Como o heap é gerenciado por você, é você quem precisa devolver a memória quando não precisar mais dela. Isso se faz com free, passando o ponteiro que o malloc devolveu:

free(v);      // devolve o bloco ao sistema; ele pode ser reaproveitado
v = NULL;     // boa pratica: o ponteiro nao aponta mais para nada valido

A regra mental é simples e inegociável:

Todo malloc deve ter, em algum momento, o seu free correspondente.

Depois do free, o bloco não é mais seu — usá-lo é um erro grave. Colocar v = NULL logo após o free é uma proteção barata: se, por acidente, alguém tentar usar v depois, ele estará em NULL (o que é fácil de detectar) em vez de apontar para memória já liberada (o que causa bugs traiçoeiros, chamados dangling pointer — ponteiro "pendurado").

O que free não faz

  • free(v) não apaga a variável v nem a zera — apenas devolve o bloco para onde ela aponta. v continua com o mesmo endereço (agora inválido); por isso o v = NULL logo em seguida.
  • Não use a memória depois do free (use-after-free), nem chame free duas vezes no mesmo ponteiro (double free). Ambos são erros sérios que aprofundaremos na Semana 13.
  • free espera exatamente o ponteiro que o malloc devolveu. Não passe um ponteiro "no meio" do bloco (ex.: free(v + 2)).

Curiosidade 💡 — e se eu esquecer o free? (vazamento de memória)

Se você aloca memória e nunca a libera, aquele bloco fica reservado até o programa terminar, mesmo sem ninguém mais usá-lo. Isso é um vazamento de memória (memory leak). Num programa que roda e fecha logo, o sistema recupera tudo no fim e "ninguém percebe". Mas num programa que fica horas ou dias rodando (um servidor, um jogo, um app) e que vaza um pouquinho a cada operação, o consumo de memória só cresce até o programa engasgar. Vamos investigar esse efeito na sexta e estudá-lo a fundo na Semana 13. Por ora, grave o par: alocou, use; terminou, libere.

7. Um exemplo completo, do início ao fim

Vamos juntar tudo num programa que faz o que motivou a semana: perguntar ao usuário quantos valores ele quer, alocar um vetor exatamente desse tamanho, preenchê-lo, calcular a soma e a média e, ao final, liberar a memória.

media_dinamica.c
#include <stdio.h>
#include <stdlib.h>   // malloc, free

int main(void) {
    int n;

    printf("Quantos numeros voce vai digitar? ");
    scanf("%d", &n);

    if (n <= 0) {
        printf("Quantidade invalida.\n");
        return 1;
    }

    // 1) ALOCA um vetor dinamico de n inteiros no heap
    int *v = malloc(n * sizeof(int));

    // 2) VERIFICA se a alocacao teve sucesso
    if (v == NULL) {
        printf("Erro: memoria insuficiente.\n");
        return 1;
    }

    // 3) USA como um vetor normal: preenche
    for (int i = 0; i < n; i++) {
        printf("Valor %d: ", i + 1);
        scanf("%d", &v[i]);
    }

    // 4) PROCESSA: soma e media
    long soma = 0;                 // long para nao estourar com muitos valores
    for (int i = 0; i < n; i++) {
        soma += v[i];
    }
    double media = (double) soma / n;   // cast para nao cair na divisao inteira

    printf("Soma:  %ld\n", soma);
    printf("Media: %.2f\n", media);

    // 5) LIBERA a memoria e anula o ponteiro
    free(v);
    v = NULL;

    return 0;
}

Percorra o programa notando os cinco passos — alocar, verificar, usar, processar, liberar. Eles são o "esqueleto" de praticamente todo uso de alocação dinâmica; vale memorizá-los nessa ordem.

Repare no (double) soma / n

A média mistura um inteiro (soma) com outro (n). Sem o cast, soma / n seria uma divisão inteira (a armadilha da Semana 01!) e a média sairia truncada. O (double) força a conta a ser feita em ponto flutuante. Alocação dinâmica é novidade, mas as armadilhas antigas continuam valendo.

8. calloc: alocar e zerar de uma vez

Existe uma prima próxima do malloc que vale conhecer: a função calloc (cleared allocation). Ela também reserva memória no heap, mas com duas diferenças:

  1. Recebe dois argumentos: a quantidade de elementos e o tamanho de cada um.
  2. Zera todos os bytes do bloco antes de devolvê-lo.
int *a = malloc(n * sizeof(int));   // conteudo INDEFINIDO (lixo de memoria)
int *b = calloc(n, sizeof(int));    // conteudo garantidamente ZERADO

A diferença essencial está no conteúdo inicial. O bloco do malloc vem com lixo (o que estava naquela região da memória) — exatamente como uma variável local não inicializada (lembra do "lixo de memória" da Semana 01?). Já o calloc garante que tudo começa em 0.

Aspecto malloc(n * sizeof(int)) calloc(n, sizeof(int))
Argumentos 1 (total de bytes) 2 (nº de elementos, tamanho de cada)
Conteúdo inicial indefinido (lixo) tudo zero
Velocidade levemente mais rápido um pouco mais lento (zera)
Quando usar vou preencher tudo mesmo quero começar zerado (ex.: contadores)

Tanto malloc quanto calloc se liberam com o mesmo free.

Alocar não é inicializar (no caso do malloc)

Um erro comum é alocar com malloc e sair somando em v[i] sem antes atribuir valor a cada posição — os valores iniciais são lixo, e o resultado será imprevisível. Ou você preenche todas as posições antes de lê-las (como no exemplo da seção 7), ou usa calloc para começar tudo em zero. Nunca leia uma posição de malloc que você ainda não escreveu.

9. Erros comuns desta semana (resumão)

Checklist de armadilhas

  • Esquecer o #include <stdlib.h> (onde vivem malloc, calloc e free).
  • Não testar NULL depois do malloc/calloc.
  • Errar a conta do tamanho: usar malloc(n) em vez de malloc(n * sizeof(int)).
  • Ler posições de um bloco de malloc antes de preenchê-las (contêm lixo).
  • Esquecer o free (vazamento de memória).
  • Usar a memória depois do free (use-after-free) ou dar free duas vezes.
  • Fazer cast desnecessário do malloc em C (e, pior, esquecer o <stdlib.h>).
  • Confundir o ponteiro (mora na pilha) com o bloco apontado (mora no heap).

👥 Quarta — Encontro

Chegue ao encontro tendo estudado o material acima e tendo executado o exemplo media_dinamica.c. O encontro é para consolidar e tirar dúvidas — não para ver o conteúdo pela primeira vez. Roteiro previsto:

  1. Revisão relâmpago: pilha × heap, o papel do ponteiro, e o "esqueleto" dos cinco passos (alocar → verificar → usar → processar → liberar).
  2. Construção ao vivo do problema-guia abaixo, desenhando a memória no quadro para separar o ponteiro (pilha) do bloco (heap).
  3. Galeria de erros: vamos provocar de propósito os erros da seção 9 — esquecer o sizeof, pular o teste de NULL, usar depois do free — para você aprender a reconhecê-los.

Sobre o encontro de quarta (28/10)

O calendário do servidor público às vezes marca recesso nesta data. Se o encontro de quarta for afetado, siga a orientação do professor (remarcação ou gravação). O conteúdo e os prazos da semana permanecem.

Problema-guia do encontro — o vetor que cresce sob demanda: ler n do usuário, alocar dinamicamente um vetor de n reais, ler os valores e reportar quantos estão acima da média. É um problema que exige dois passos sobre os dados (primeiro calcular a média, depois contar), o que só é possível porque guardamos todos os valores — e para isso precisamos de um vetor do tamanho certo, decidido em execução.

acima_da_media.c
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    printf("Quantos valores? ");
    scanf("%d", &n);

    if (n <= 0) {
        printf("Quantidade invalida.\n");
        return 1;
    }

    double *v = malloc(n * sizeof(double));   // vetor dinamico de double
    if (v == NULL) {
        printf("Erro: memoria insuficiente.\n");
        return 1;
    }

    // Leitura (lembre: double no scanf usa %lf)
    double soma = 0.0;
    for (int i = 0; i < n; i++) {
        printf("Valor %d: ", i + 1);
        scanf("%lf", &v[i]);
        soma += v[i];
    }

    double media = soma / n;

    // Segunda passada: contar quantos superam a media
    int acima = 0;
    for (int i = 0; i < n; i++) {
        if (v[i] > media) {
            acima++;
        }
    }

    printf("Media: %.2f\n", media);
    printf("%d valor(es) acima da media.\n", acima);

    free(v);
    v = NULL;
    return 0;
}

Perguntas para pensar antes do encontro: por que precisamos guardar os valores num vetor, em vez de só ir somando? (Dica: para comparar com a média, precisamos dos valores depois de a média estar pronta.) E por que soma aqui é double, e não long como no exemplo da segunda?


✍️ Sexta — Estudo dirigido

Sobre o Beecrowd nesta semana

Alocação dinâmica não é o foco dos problemas iniciais do Beecrowd — a maioria deles aceita vetores de tamanho fixo tranquilamente. Por isso, o exercício desta sexta é diferente: pegue problemas de vetores que você já sabe resolver e refaça-os usando alocação dinâmica, lendo o tamanho n do usuário (ou da entrada) e alocando o vetor com malloc. O objetivo é fixar o "esqueleto" dos cinco passos, não vencer um problema novo. Sugestões de bons candidatos para essa releitura:

Problema Como aplicar alocação dinâmica
1005 / 1006 Médias aloque o vetor de notas com malloc (mesmo sendo poucas)
1010 Cálculo Simples pratique alocar e liberar mesmo em casos pequenos
1172 Vetor de Trabalho leia o tamanho e aloque dinamicamente
1173 Preenchendo Vetor I aloque n posições e preencha num laço
1174 Vetor de Verdade leia, aloque, filtre e imprima
1180 Menor e Posição aloque o vetor, ache o menor e a posição, depois free

O código passa (o Beecrowd só olha a saída), mas o treino de alocar/verificar/liberar é seu.

Desafios para escrever do zero

  1. Estatísticas de um vetor dinâmico: ler n, alocar um vetor de n inteiros, preencher e imprimir soma, média, maior e menor. Não esqueça o free no final.
  2. Nomes/valores sob demanda: ler n e, num laço, ler n valores reais (double, com %lf) para um vetor alocado dinamicamente; ao final, imprimir a lista na ordem inversa da leitura.
  3. Cópia para um novo vetor dinâmico: dado um vetor v de tamanho n (pode ser fixo), alocar um segundo vetor w, também dinâmico, do mesmo tamanho, e copiar todos os elementos de v para w. Ao terminar, libere w.
  4. Função que devolve um ponteiro (importante!): escrever uma função com a assinatura
int *cria_vetor(int n);

que aloca internamente um vetor de n inteiros, preenche cada posição com i * i e devolve o ponteiro para quem chamou. Na main, use o retorno como um vetor normal e, ao final, dê free. Reflita: por que isso funciona com o heap, mas não funcionaria se a função declarasse um vetor local int v[n]; e tentasse devolvê-lo? (Volte à curiosidade da seção 1.1.)

Para investigar (curiosidade prática)

  • Provocando um vazamento: escreva um laço que executa muitas vezes (ex.: um milhão) e, a cada volta, faz malloc de um bloco grande sem o free correspondente. Rode e observe (no Gerenciador de Tarefas / htop / top) o consumo de memória do programa subir sem parar. Depois adicione o free dentro do laço e compare. O que muda?
  • Esquecendo o teste de NULL: peça um n absurdamente grande (algo que o sistema não consiga alocar) e tente usar v[0] sem ter testado NULL. Observe o programa travar (segmentation fault). Depois, coloque o teste de volta e veja o programa falhar com elegância, imprimindo uma mensagem de erro em vez de morrer. Discuta: por que a segunda versão é preferível num programa de verdade?

✅ Checklist de autoavaliação

Marque com sinceridade — se algum item não estiver ✅, volte à seção correspondente.

  • Explico a diferença entre tamanho fixo em compilação e decidido em execução, e por que a segunda situação pede alocação dinâmica.
  • Sei situar a pilha e o heap e digo em qual deles fica o ponteiro e em qual fica o bloco alocado.
  • Uso sizeof para calcular o tamanho de um bloco (n * sizeof(tipo)) e não "chuto" o número de bytes.
  • Aloco com malloc, guardo o retorno num ponteiro do tipo certo e testo NULL antes de usar.
  • Uso a memória alocada como um vetor comum (v[i]).
  • Libero com free ao terminar e faço v = NULL em seguida.
  • Sei o que é um vazamento de memória e por que todo malloc precisa do seu free.
  • Sei a diferença entre malloc (lixo) e calloc (zerado) e quando cada um convém.

🔗 Referências e para se aprofundar

  • Material 2026.1 — Aula 11: Ponteiros e Alocação Dinâmica.
  • BACKES, André. Linguagem C: completa e descomplicada (ponteiros e alocação dinâmica de memória).
  • KERNIGHAN, B.; RITCHIE, D. The C Programming Language (o clássico "K&R"), cap. 5 — ponteiros e gerenciamento de memória (malloc/free).
  • Documentação de referência: cppreference — malloc (e, ao lado, calloc, realloc e free).
  • Prática: Beecrowd.

Antes de seguir para a Semana 13

Você deu o primeiro passo no gerenciamento manual de memória — o que separa quem "usa" C de quem entende C. Certifique-se de que o "esqueleto" dos cinco passos (alocar → verificar → usar → processar → liberar) está automático nas suas mãos. Na Semana 13 levamos a alocação dinâmica adiante: realloc para redimensionar um bloco já alocado, matrizes dinâmicas (ponteiro para ponteiro) e um mergulho nos vazamentos de memória e nos erros clássicos (use-after-free, double free). Tudo isso se apoia no que você firmou aqui — então não avance sem ter digitado, executado e liberado os exemplos desta semana.