root@malloc:~$ ls
root@malloc:~/blog$ cat stack-e-heap-diferencas
# Stack e Heap: diferenças | 19/06/2025

Tá, você está aprendendo C e chegou no malloc(). Em cinco minutos alguém manda “joga no heap” como se fosse resposta universal.

Não é. Stack e heap resolvem problemas diferentes de ciclo de vida da memória. Se você não entende essa diferença, você escreve bug previsível.

Stack

Quando uma função é chamada, o processo cria um stack frame com variáveis locais, parâmetros e endereço de retorno. Quando a função termina, esse frame é descartado automaticamente.

void foo() {
    int x = 10;
    char buf[64];
}
// aqui fora x e buf já não existem mais

É rápido porque o runtime só move o ponteiro da stack, sem busca de bloco livre nem metadata complexa de alocador. A contrapartida é limite fixo por thread/processo, que no Linux costuma ficar por volta de 8MB. Estourou esse limite, o processo cai com segfault.

O caso clássico é recursão sem condição de parada:

void f(int n) {
    int buf[1000]; // ~4KB por chamada (int de 4 bytes)
    f(n + 1);      // nunca para
}

Cada chamada empilha mais um frame com cerca de 4KB só desse buffer, fora overhead da própria chamada. Em poucas iterações você exaure a stack. Isso é stack overflow, literalmente crescimento da pilha além do limite.

Heap

Heap é memória dinâmica alocada em runtime. Você pede com malloc(), usa e precisa devolver com free().

int n;
scanf("%d", &n);

int *lista = malloc(n * sizeof(int));
if (lista == NULL)
    return 1;

// usa lista...

free(lista);

Esse padrão existe porque n só é conhecido durante execução. Na stack, esse tamanho variável pode ser inviável ou inseguro dependendo do cenário. No heap você ganha flexibilidade, mas vira responsável por ownership e tempo de vida.

Se errar esse controle, abre classe inteira de falha: leak, use-after-free e double-free. Em segurança ofensiva, especialmente em código nativo, isso pode evoluir de crash pra corrupção de heap allocator e execução de código.

void problema() {
    char *buf = malloc(256);

    if (deu_erro)
        return; // bug: saiu sem free()

    free(buf);
}

O bug aqui é caminho de erro sem liberação. Isolado parece pequeno, mas repetido em serviço de longa duração vira crescimento contínuo de memória e degradação operacional.

”Mas por que não usar sempre heap então?”

Porque heap não é atalho sem custo.

Você troca simplicidade por gestão manual de estado. Isso aumenta superfície pra bug de ciclo de vida, principalmente quando o código cresce e múltiplos fluxos de erro aparecem.

Quando usar cada um

Stack pra dado local, pequeno e de vida curta no escopo da função. Heap quando o tamanho só aparece em runtime, quando o objeto precisa sobreviver ao escopo atual ou quando o volume ficaria perigoso pra stack.

Em segurança isso importa mais ainda. Stack overflow e heap overflow não são a mesma coisa: mudam primitiva, técnica de exploração e mitigação. Se você trata como detalhe de implementação, perde sinal técnico na hora de auditar vulnerabilidade.