📚 Capítulo do curso: 15 — Alocação Dinâmica no Heap, Valgrind e Prevenção de Leaks da Especialização em Sistemas com C. Leia o capítulo antes de começar.
Gerenciador de memória com malloc, realloc seguro e zero memory leaks.
| ⏱️ Tempo estimado | 📦 Você vai entregar | ✅ Pronto quando |
|---|---|---|
| 55 min | main.c |
a capacidade cresce 2 → 4 → 8 → 16, os 10 valores são impressos e tudo é liberado |
malloc e validar seu retorno contra NULL.std::vector (C++), ArrayList (Java) e list (Python).realloc de forma segura, sem perder o ponteiro original em caso de falha.malloc/realloc termina em um free correspondente — mentalidade “Valgrind-clean”.Arrays de tamanho fixo em C têm um problema: você precisa saber o tamanho antes de compilar. Um vetor dinâmico resolve isso guardando, além do array em si, dois números: quantos elementos ele pode guardar agora (capacidade) e quantos ele de fato guarda (tamanho). Quando tamanho alcança capacidade, o vetor pede mais memória ao sistema operacional via realloc, tipicamente dobrando a capacidade — estratégia que mantém o custo médio de inserção baixo mesmo crescendo o array centenas de vezes.
Seu programa (main.c) deve:
realloc antes de inserir o próximo elemento, imprimindo a nova capacidade a cada crescimento.malloc/realloc contra NULL, encerrando com mensagem de erro (sem crashar com segfault) se a alocação falhar.free.| ID | Requisito |
|---|---|
| RF01 | O programa deve compilar sem nenhum aviso com gcc -Wall -Wextra -Wpedantic -std=c17. |
| RF02 | O vetor deve iniciar com malloc para capacidade 2, validando o retorno contra NULL. |
| RF03 | O programa deve inserir exatamente 10 elementos inteiros, um por vez, em um laço. |
| RF04 | Sempre que tamanho == capacidade, deve dobrar a capacidade com realloc antes de inserir o próximo elemento, e imprimir a nova capacidade. |
| RF05 | O retorno de realloc deve ser guardado em uma variável temporária antes de sobrescrever o ponteiro original, para não perder a referência em caso de falha (NULL). |
| RF06 | Ao final, deve imprimir todos os 10 elementos inseridos, na ordem de inserção. |
| RF07 | Deve liberar toda a memória alocada com free antes de retornar, deixando o programa livre de vazamentos. |
graph LR
A["malloc(capacidade=2)"] --> B{"tamanho == capacidade?"}
B -- "sim" --> C["realloc(capacidade * 2)\nvalida contra NULL"]
C --> D["insere elemento"]
B -- "nao" --> D
D --> E{"todos os 10\nja inseridos?"}
E -- "nao" --> B
E -- "sim" --> F["imprime vetor final"]
F --> G["free(vetor)"]
style A fill:#0284c7,stroke:#38bdf8,color:#ffffff
style C fill:#7c3aed,stroke:#a78bfa,color:#ffffff
style G fill:#059669,stroke:#34d399,color:#ffffff
O mesmo do Miniprojeto 01: gcc 8 ou mais novo. No Windows, rode chcp 65001 se os emojis aparecerem quebrados.
🎯 Objetivo: partir de um código que compila e roda, antes de escrever qualquer linha.
⌨️ Faça: crie a pasta c_mini_15, salve o código abaixo como main.c e rode:
gcc -Wall -Wextra -Wpedantic -std=c17 main.c -o app
./app # Linux/macOS
.\app.exe # Windows (PowerShell)
📄 src/esqueleto.c
/*
* Miniprojeto 15: Vetor Dinamico Auto-expansivel no Heap (Dynamic Array)
* Especializacao em Engenharia de Sistemas em C
*/
#include <stdio.h>
#include <stdlib.h>
#define CAPACIDADE_INICIAL 2
#define TOTAL_ELEMENTOS 10
int main(void) {
printf("====================================================\n");
printf(" ⚡ Miniprojeto 15: Vetor Dinamico Auto-expansivel\n");
printf("====================================================\n");
size_t capacidade = CAPACIDADE_INICIAL;
size_t tamanho = 0;
// TODO 1: declare "int *vetor" recebendo malloc(capacidade * sizeof(int)) e
// valide contra NULL (imprima erro em stderr e retorne EXIT_FAILURE).
printf("\nCapacidade inicial: %zu\n\n", capacidade);
for (int i = 0; i < TOTAL_ELEMENTOS; i++) {
// TODO 2: se tamanho == capacidade, dobre a capacidade:
// - calcule nova_capacidade = capacidade * 2
// - chame realloc(vetor, nova_capacidade * sizeof(int)) e
// guarde o resultado em um ponteiro TEMPORARIO (ex: temp)
// - se temp == NULL, imprima erro, de' free(vetor) e retorne
// EXIT_FAILURE (NAO perca o ponteiro original antes de checar)
// - se deu certo: vetor = temp; capacidade = nova_capacidade;
// - imprima a nova capacidade
int valor = (i + 1) * 10;
// TODO 3: insira "valor" na posicao "tamanho" do vetor e
// incremente "tamanho" (e apague o printf abaixo).
printf("Ainda nao inserido: %d\n", valor);
}
printf("\n--- Conteudo final do vetor (%zu elementos) ---\n", tamanho);
// TODO 4: percorra "vetor" de 0 ate "tamanho" e imprima cada elemento.
// TODO 5: libere "vetor" com free() e atribua NULL a ele.
printf("\n====================================================\n");
printf(" ✅ Memoria liberada. Status: Operacional e Validado!\n");
printf("====================================================\n");
return EXIT_SUCCESS;
}
✅ Checkpoint: zero avisos do gcc, e a saída é:
🖥️ Saída esperada:
====================================================
⚡ Miniprojeto 15: Vetor Dinamico Auto-expansivel
====================================================
Capacidade inicial: 2
Ainda nao inserido: 10
Ainda nao inserido: 20
Ainda nao inserido: 30
Ainda nao inserido: 40
Ainda nao inserido: 50
Ainda nao inserido: 60
Ainda nao inserido: 70
Ainda nao inserido: 80
Ainda nao inserido: 90
Ainda nao inserido: 100
--- Conteudo final do vetor (0 elementos) ---
====================================================
✅ Memoria liberada. Status: Operacional e Validado!
====================================================
Os 10 valores são gerados, mas ainda não há onde guardá-los.
🎯 Objetivo: pedir memória ao sistema e nunca confiar cegamente na resposta.
⌨️ Faça: int *vetor = malloc(capacidade * sizeof(int)); e, se vetor == NULL, imprima um erro em stderr e return EXIT_FAILURE;.
✅ Checkpoint: compila sem avisos e a saída não muda. A alocação existe, mas ainda não é usada.
🧯 Se der errado: implicit declaration of function 'malloc' → falta #include <stdlib.h>.
realloc seguro (TODO 2 e 3)🎯 Objetivo: dobrar a capacidade sem perder o bloco antigo se o realloc falhar.
⌨️ Faça:
if (tamanho == capacidade): calcule nova_capacidade = capacidade * 2, faça int *temp = realloc(vetor, nova_capacidade * sizeof(int));. Se temp == NULL, faça free(vetor) e saia com erro. Senão, vetor = temp;, atualize a capacidade e imprima >> Capacidade cheia! Vetor cresceu para: %zu.printf("Ainda nao inserido...") por vetor[tamanho] = valor; tamanho++; e uma linha Inserido vetor[...].✅ Checkpoint: aparecem exatamente três crescimentos: para 4 (antes do 30), para 8 (antes do 50) e para 16 (antes do 90).
🧯 Se der errado:
vetor[2] foi escrito num bloco de 2 posições.vetor = realloc(vetor, ...) direto funciona até o dia em que o realloc devolve NULL. Nesse dia você perde o único ponteiro para o bloco antigo, e ele vaza.🎯 Objetivo: fechar o ciclo de vida da memória.
⌨️ Faça: percorra de 0 a tamanho imprimindo cada valor. Depois, free(vetor); vetor = NULL;.
✅ Checkpoint: --- Conteudo final do vetor (10 elementos) --- seguido de 10 20 30 40 50 60 70 80 90 100.
🧯 Se der errado: no Linux ou WSL, valgrind --leak-check=full ./app deve terminar com All heap blocks were freed -- no leaks are possible. Se aparecer definitely lost, faltou o free.
🎯 Objetivo: validar a saída completa e o código de retorno.
✅ Checkpoint: a saída é esta, e o código de retorno é 0 (echo $? no Linux/macOS ou echo $LASTEXITCODE no PowerShell):
🖥️ Saída esperada:
====================================================
⚡ Miniprojeto 15: Vetor Dinamico Auto-expansivel
====================================================
Capacidade inicial: 2
Inserido vetor[0] = 10 (tamanho=1, capacidade=2)
Inserido vetor[1] = 20 (tamanho=2, capacidade=2)
>> Capacidade cheia! Vetor cresceu para: 4
Inserido vetor[2] = 30 (tamanho=3, capacidade=4)
Inserido vetor[3] = 40 (tamanho=4, capacidade=4)
>> Capacidade cheia! Vetor cresceu para: 8
Inserido vetor[4] = 50 (tamanho=5, capacidade=8)
Inserido vetor[5] = 60 (tamanho=6, capacidade=8)
Inserido vetor[6] = 70 (tamanho=7, capacidade=8)
Inserido vetor[7] = 80 (tamanho=8, capacidade=8)
>> Capacidade cheia! Vetor cresceu para: 16
Inserido vetor[8] = 90 (tamanho=9, capacidade=16)
Inserido vetor[9] = 100 (tamanho=10, capacidade=16)
--- Conteudo final do vetor (10 elementos) ---
10 20 30 40 50 60 70 80 90 100
====================================================
✅ Memoria liberada. Status: Operacional e Validado!
====================================================
Só abra o gabarito depois de chegar até aqui:
src/main.c📄 src/main.c
/*
* Miniprojeto 15: Vetor Dinamico Auto-expansivel no Heap (Dynamic Array)
* Especializacao em Engenharia de Sistemas em C
*/
#include <stdio.h>
#include <stdlib.h>
#define CAPACIDADE_INICIAL 2
#define TOTAL_ELEMENTOS 10
int main(void) {
printf("====================================================\n");
printf(" ⚡ Miniprojeto 15: Vetor Dinamico Auto-expansivel\n");
printf("====================================================\n");
size_t capacidade = CAPACIDADE_INICIAL;
size_t tamanho = 0;
int *vetor = malloc(capacidade * sizeof(int));
if (vetor == NULL) {
fprintf(stderr, "ERRO: falha ao alocar memoria inicial.\n");
return EXIT_FAILURE;
}
printf("\nCapacidade inicial: %zu\n\n", capacidade);
for (int i = 0; i < TOTAL_ELEMENTOS; i++) {
if (tamanho == capacidade) {
size_t nova_capacidade = capacidade * 2;
/* Guardamos o resultado do realloc em "temp" primeiro: se a
* alocacao falhar, "vetor" continua valido e podemos liberar
* a memoria original sem vaza-la (nunca faca
* "vetor = realloc(vetor, ...)" diretamente). */
int *temp = realloc(vetor, nova_capacidade * sizeof(int));
if (temp == NULL) {
fprintf(stderr, "ERRO: falha ao realocar memoria.\n");
free(vetor);
return EXIT_FAILURE;
}
vetor = temp;
capacidade = nova_capacidade;
printf(">> Capacidade cheia! Vetor cresceu para: %zu\n", capacidade);
}
int valor = (i + 1) * 10;
vetor[tamanho] = valor;
tamanho++;
printf("Inserido vetor[%zu] = %d (tamanho=%zu, capacidade=%zu)\n",
tamanho - 1, valor, tamanho, capacidade);
}
printf("\n--- Conteudo final do vetor (%zu elementos) ---\n", tamanho);
for (size_t i = 0; i < tamanho; i++) {
printf("%d ", vetor[i]);
}
printf("\n");
free(vetor);
vetor = NULL;
printf("\n====================================================\n");
printf(" ✅ Memoria liberada. Status: Operacional e Validado!\n");
printf("====================================================\n");
return EXIT_SUCCESS;
}
Por que isso importa: repare a sequência de capacidades impressas: 2 → 4 → 8 → 16. Mesmo inserindo 10 elementos, o vetor só realocou 3 vezes, não 8 — essa é a mágica do crescimento geométrico, que é exatamente o que std::vector::push_back faz por baixo dos panos em C++.
Adicione uma função void inserir(int **vetor, size_t *tamanho, size_t *capacidade, int valor) que encapsule toda a lógica de crescimento (recebendo os três valores por ponteiro, já que main precisa enxergar as mudanças). Depois, insira 1000 elementos em vez de 10 e conte, com um contador extra, quantas vezes o realloc foi de fato chamado — compare com log2(1000) para confirmar que o crescimento continua sendo logarítmico no número de realocações.
✅ Checkpoint: com 1000 elementos a partir da capacidade 2, o realloc é chamado 9 vezes (2 → 4 → … → 1024), perto de log2(1000) ≈ 10. A capacidade final é 1024.
📋 Projetos do curso de C | 📚 Teoria do Capítulo 15 | 🗂️ Hub de projetos