Bem-vindo ao décimo quinto capítulo da Especialização em Engenharia de Sistemas com Linguagem C (C17/C23)! ⚡

Até agora, todas as nossas variáveis e vetores possuíam tamanho fixo determinado em tempo de compilação e alocado na memória Stack. No entanto, sistemas reais (bancos de dados, navegadores, servidores web, compiladores) não conhecem a quantidade de dados de antemão. A Alocação Dinâmica no Heap permite requisitar blocos arbitrários de memória em tempo de execução. Dominar o ciclo de vida do Heap e ferramentas como Valgrind e AddressSanitizer é indispensável para construir softwares de nível industrial livres de falhas de memória.

Alocação Dinâmica de Memória no Heap, Valgrind e Prevenção de Leaks em C


🗺️ Mapa Conceitual do Capítulo

graph TD
    A["Alocação Dinâmica no Heap"] --> B["1. Funções de <stdlib.h>"]
    A --> C["2. O Ciclo de Vida de 5 Etapas"]
    A --> D["3. Vulnerabilidades Clássicas"]
    A --> E["4. Diagnóstico com Valgrind & ASan"]

    B --> B1["malloc, calloc, realloc e free"]
    C --> C1["Alocação -> Verificação NULL -> Uso -> free() -> ptr = NULL"]
    D --> D1["Memory Leak, Dangling Pointer, Use-After-Free, Double Free"]
    E --> E1["--leak-check=full e gcc -fsanitize=address"]

💾 1. Stack vs Heap: As Duas Regiões da Memória

Característica Memória Stack (Pilha) Memória Heap (Monte)
Gerenciamento Automático pelo compilador na entrada/saída de funções. Manual pelo desenvolvedor via malloc() e free().
Capacidade Limitada (1 MB a 8 MB). Risco de Stack Overflow. Gigabytes (limitada apenas pela RAM física do sistema).
Tempo de Vida Presa ao escopo da função ou bloco. Persiste até ser explicitamente liberada com free().
Velocidade Extremamente rápida (apenas decrementa %rsp). Ligeiramente mais lenta (busca de blocos livres pelo kernel).

🛠️ 2. As Funções Canônicas da <stdlib.h>

1. malloc() (Memory Allocation)

Requisita uma quantidade bruta de bytes contíguos. Atenção: Os dados contêm “lixo” residual da memória anterior:

int *v = (int*)malloc(100 * sizeof(int)); // Aloca 400 bytes

2. calloc() (Clear Allocation)

Aloca a memória e inicializa todos os bits com zero (0x00):

int *v = (int*)calloc(100, sizeof(int)); // Aloca 400 bytes zerados

3. free()

Devolve o bloco de memória ao sistema operacional:

free(v);    // Libera a memória
v = NULL;   // Boa prática: anula o ponteiro contra Dangling Pointer

🔒 3. O Padrão Seguro de Redimensionamento com realloc()

[!CAUTION] Anti-Padrão Fatal: ptr = realloc(ptr, novoTamanho); Se o sistema não tiver memória livre, realloc() retornará NULL. Ao sobrescrever ptr, você perde o endereço do bloco original, gerando um Vazamento de Memória (Memory Leak) instantâneo e irrecuperável!

O Idioma Seguro Recomendado pelo CERT C:

size_t novoTamanho = 200 * sizeof(int);
void *temp = realloc(v, novoTamanho);

if (temp != NULL) {
    v = (int*)temp; // ✅ Realocação com sucesso
} else {
    fprintf(stderr, "[ERRO]: Falha ao redimensionar! Memória original preservada.\n");
    free(v); // Libera o bloco original antes de abortar
    v = NULL;
}

⚠️ 4. As 4 Armadilhas Fatais de Memória

  1. Memory Leak (Vazamento de Memória): Alocar com malloc e perder o ponteiro sem chamar free(). A memória do processo cresce até travar o servidor.
  2. Dangling Pointer (Ponteiro Fantasma): Ponteiro que continua apontando para um endereço após o free().
  3. Use-After-Free: Ler ou escrever em um bloco de memória que já foi liberado.
  4. Double Free: Executar free(ptr) duas vezes sobre o mesmo ponteiro, corrompendo as estruturas internas do alocador do sistema.

🛡️ 5. Ferramentas Profissionais de Diagnóstico

1. Valgrind Memcheck

Detecta qualquer byte não liberado ou acesso ilegal:

gcc -g programa.c -o programa
valgrind --leak-check=full --show-leak-kinds=all ./programa

Resultado Esperado: All heap blocks were freed -- no leaks are possible.

2. AddressSanitizer (ASan)

Instrumentador do GCC/Clang que aborta o programa imediatamente na linha exata do erro:

gcc -fsanitize=address -g programa.c -o programa
./programa

🔍 6. Diagnóstico & Resolução de Problemas (Troubleshooting)

Sintoma Observado Causa Provável Como Resolver
O consumo de memória da aplicação só aumenta com o tempo Memory Leak: chamadas a malloc/calloc dentro de loops sem o respectivo free(). Audite com Valgrind e certifique-se de que cada alocação possua um free().
Crash aleatório ao executar free(ptr) Double Free ou corrupção do cabeçalho do Heap (Heap Corruption). Sempre zere o ponteiro após o free (ptr = NULL;) e verifique índices fora do limite.
malloc retorna NULL imediatamente Memória física/swap esgotada ou pedido de tamanho absurdo/negativo por overflow de size_t. Sempre valide if (ptr == NULL) antes de acessar a memória.

🏆 7. Desafio Prático de Consolidação

Enunciado do Desafio: Desenvolva um programa em C chamado vetor_dinamico_crescente.c que implemente um Vetor Dinâmico Auto-Redimensionável (equivalente ao std::vector do C++):

  1. Inicie com uma capacidade inicial de 2 elementos usando malloc().
  2. Implemente a função bool pushBack(int **array, size_t *tamanho, size_t *capacidade, int novoValor);.
  3. Sempre que tamanho == capacidade, dobre a capacidade (capacidade * 2) usando o padrão seguro de realloc com ponteiro temporário.
  4. Na função main, insira 10 números em sequência, exibindo a expansão de capacidade do Heap a cada redimensionamento.
  5. Libere toda a memória com free() e anule o ponteiro no final.
🔍 Ver Solução Comentada do Desafio
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    size_t cap = 2;
    size_t len = 0;
    int *arr = (int*)malloc(cap * sizeof(int));
    if (arr == NULL) return 1;

    for (int val = 10; val <= 50; val += 10) {
        if (len == cap) {
            size_t novaCap = cap * 2;
            int *temp = (int*)realloc(arr, novaCap * sizeof(int));
            if (temp == NULL) {
                free(arr);
                return 1;
            }
            arr = temp;
            cap = novaCap;
        }
        arr[len++] = val;
    }

    printf("Vetor Dinamico Expandido (Tamanho: %zu, Capacidade: %zu):\n", len, cap);
    for (size_t i = 0; i < len; i++) printf("%d ", arr[i]);
    printf("\n");

    free(arr);
    arr = NULL;
    printf("Memoria liberada com sucesso! Valgrind Clean! ✅\n");

    return 0;
}

🧭 Navegação Rápida

| 📖 Teoria | 📊 Slides | 🧠 Quiz | 💻 Exemplos | 🧩 Exercícios | | :— | :— | :— | :— | :— | | Ler Teoria | Ver Slides | Fazer Quiz | Ver Exemplos | Praticar Exercícios |


🧭 Navegação do Capítulo: ⬅️ Capítulo Anterior · 📚 Sumário do Módulo · ➡️ Próximo Capítulo