Heap e alocação dinâmica
A stack é automática mas limitada. O heap é manual mas ilimitado. malloc, new, free, delete — e o problema da fragmentação que surge quando você aloca e libera muitas vezes.
Quando a stack não é suficiente
A stack é conveniente: você declara uma variável e ela some sozinha quando a função retorna.
Mas tem dois limites sérios. Primeiro, o tamanho precisa ser conhecido em tempo
de compilação — você não pode declarar int arr[n] se n só é conhecido em runtime.
Segundo, a vida útil está amarrada ao frame — o dado desaparece quando a função
retorna, mesmo que outros trechos do programa ainda precisem dele.
O heap resolve os dois: você aloca qualquer tamanho em runtime e o bloco persiste até você
explicitamente liberá-lo. O preço é responsabilidade: você precisa lembrar de chamar
free(). Esquecer = memory leak. Liberar duas vezes = heap corrompido. Usar após
liberar = undefined behavior.
Aloque, libere, observe a fragmentação
Cada célula do grid representa 4 bytes. Aloque blocos de tamanhos diferentes e depois libere-os em ordem alternada para ver a fragmentação emergir.
malloc, calloc, realloc, free
// Alocação dinâmica em C int* arr = malloc(n * sizeof(int)); // aloca n inteiros no heap if (arr == NULL) { perror("malloc"); exit(1); } // sempre checar! arr[0] = 42; arr[n - 1] = 99; free(arr); // devolve ao alocador arr = NULL; // evita dangling pointer // calloc: aloca E zera int* zeros = calloc(n, sizeof(int)); // realloc: redimensiona (pode mover o bloco!) arr = realloc(arr, 2 * n * sizeof(int)); // atenção: o ponteiro antigo pode ser invalidado
Por dentro do alocador
// O que o alocador de memória faz por dentro // O heap é uma lista de blocos livres e usados: // [USADO·16B][LIVRE·32B][USADO·8B][LIVRE·64B][LIVRE·16B]... // malloc(20): busca first-fit ou best-fit na lista de livres // → acha o bloco de 32B // → divide: [USADO·20B][LIVRE·12B] // → retorna ponteiro para os 20B // free(ptr): // → marca o bloco como livre // → tenta fundir com blocos livres adjacentes (coalescência) // [LIVRE·20B][LIVRE·12B] → [LIVRE·32B] ← evita fragmentação // Fragmentação externa: muitos buracos pequenos, nenhum grande // o suficiente para a próxima alocação — mesmo que a soma seja ok // [LIVRE·8B][USADO·20B][LIVRE·8B] → malloc(16) falha!
Construindo um alocador simples
Mini projeto: implemente um my_malloc e my_free em C usando um array estático de 4096 bytes como "heap simulado". Mantenha uma lista encadeada de blocos livres e use first-fit para alocar. Imprima o estado da lista após cada operação.
Projeto principal: escreva um alocador de pool (pool allocator): pré-aloca um bloco grande e entrega sempre blocos de tamanho fixo (ex: 64 bytes). Compare latência de alocação com malloc padrão em 1 milhão de alocações/liberações. Por que o pool é mais rápido? Quando não é a escolha certa?
Desafio extra: adicione ao seu alocador detecção de double-free: ao liberar, verifique se o bloco já está marcado como livre. Adicione também um "canary value" (valor mágico) no final de cada bloco alocado e verifique na hora do free se o canary foi sobrescrito — indicando buffer overflow.
Teste sua intuição
Onde você encontra isso
Bancos de dados
Sistemas como PostgreSQL gerenciam seu próprio pool de memória (palloc/pfree) em vez de usar malloc diretamente. Isso permite liberar toda a memória de uma transação de uma só vez com uma chamada — sem rastrear cada alocação individual — e evita fragmentação do heap do alocador padrão.
Arenas em compiladores
LLVM e GCC usam "arenas" (bump allocators): alocam um bloco grande de uma vez e distribuem objetos sequencialmente com um simples incremento de ponteiro. Liberar todos os objetos de uma fase de compilação é uma única operação. Alocação é O(1) e fragmentação zero — ao custo de não poder liberar objetos individualmente.
C++ smart pointers
unique_ptr libera automaticamente ao sair de escopo (sem GC). shared_ptr usa contagem de referências para múltiplos donos. weak_ptr observa sem segurar. Em código moderno de C++, malloc e free manual são raros — os smart pointers encapsulam o ciclo de vida do heap com a segurança de tipos em tempo de compilação.