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.