Onde um valor é armazenado quando você declara uma variável?
Responder “na memória” está apenas parcialmente correto. Mesmo dentro da memória, valores colocados na stack e no heap têm ciclos de vida completamente diferentes.
O erro stack overflow, a diferença de desempenho entre classes e structs e o motivo de o ARC ser necessário só podem ser explicados entendendo essas duas áreas.
Neste artigo, veremos como stack e heap funcionam, por que há diferença de velocidade e o que vai para cada um no Swift.
Aqui estamos falando das áreas de memória. A estrutura de dados LIFO é tratada separadamente em Stack e fila no Swift, e a estrutura de árvore que implementa filas de prioridade, em Heap e fila de prioridade.
Vamos começar pelo resumo dos pontos principais.
- Stack: área gerenciada automaticamente, que cresce com chamadas de função e desaparece no retorno. É rápida.
- Heap: área dinâmica usada em tempo de execução no tamanho necessário. É flexível, mas tem custo de gerenciamento.
- A essência da diferença de velocidade está em “como alocar e desalocar” e “quem faz a limpeza”.
- No Swift, structs geralmente ficam na stack, enquanto instâncias de classes ficam no heap.
Stack: pratos empilhados
A stack armazena informações das chamadas de função. Como o nome sugere, é como uma pilha de pratos: uma estrutura LIFO em que o último prato colocado é o primeiro retirado.
Ao chamar uma função, suas variáveis locais, parâmetros e endereço de retorno são empilhados juntos em um stack frame. Quando a função retorna, todo o frame desaparece.
É aqui que aparecem as vantagens da stack.
A alocação é rápida. Basta mover o stack pointer para cima. Não é preciso procurar espaço livre.
A desalocação também é gratuita. Quando a função termina, basta mover o ponteiro de volta. Não é preciso decidir quem fará a limpeza.
Mas há restrições. O tamanho precisa ser definido em tempo de compilação, os dados sempre desaparecem quando a função termina e o limite total é pequeno (normalmente alguns MB). Se uma função recursiva continuar chamando a si mesma sem condição de término, os frames ultrapassarão o limite. Esse erro é o stack overflow.
Heap: um depósito amplo que exige gerenciamento
O heap é uma área usada em tempo de execução conforme a necessidade. Não é preciso conhecer o tamanho antecipadamente, ele sobrevive ao fim da função e é muito maior.
O custo é o gerenciamento.
A alocação é relativamente lenta. É preciso encontrar espaço livre do tamanho solicitado e coordenar solicitações simultâneas de várias threads.
Alguém precisa ser responsável pela desalocação. Como o heap não desaparece quando a função termina, alguém precisa decidir quando ele deixou de ser usado. Em C, o programador chama free diretamente; Java faz a limpeza periódica com um coletor de lixo; Swift usa ARC, que conta as referências e desaloca quando chegam a zero.
Um erro nessa decisão causa dois problemas: desalocar tarde demais gera vazamento de memória; desalocar cedo demais causa acesso à memória liberada (um ponteiro pendente).
O que vai para onde no Swift
A documentação oficial do Swift e as sessões da WWDC repetem este modelo: tipos por valor (structs e enums) são normalmente alocados na stack, enquanto tipos por referência (classes) são alocados no heap.
struct PointStruct { var x, y: Double }
class PointClass { var x = 0.0, y = 0.0 }
func run() {
let a = PointStruct(x: 1, y: 2) // Inteiro dentro do stack frame
let b = PointClass() // Alocado no heap; apenas a referência fica na stack
}
O valor da struct a entra inteiro no stack frame. Quando a função termina, ele desaparece com o frame, portanto não precisa de contagem de referências.
A instância da classe b fica no heap, e a stack armazena apenas seu endereço. Como ela pode ser referenciada de vários lugares, o ARC precisa contar as referências.
Essa é a base de desempenho da recomendação da Apple de considerar structs primeiro: os custos de alocação no heap, contagem de referências e locks são eliminados.
No entanto, “struct = sempre stack” está errado. Uma struct armazenada como propriedade de uma classe vive no heap junto com ela, e tipos como String e Array são structs externamente, mas mantêm seus buffers de dados reais no heap. O correto é entender que structs atendem às condições para poderem ficar na stack.
Perguntas de acompanhamento comuns em entrevistas
“Por que a stack é mais rápida que o heap?” — Na stack, alocação e desalocação terminam com o movimento de um ponteiro; no heap, é preciso procurar espaço livre e gerenciar a liberação (contagem de referências e GC).
“Por que ocorre stack overflow?” — O limite da stack é pequeno; recursão profunda ou variáveis locais enormes fazem os frames ultrapassá-lo.
“Variáveis locais ficam sempre na stack?” — Não. Se uma variável local for um tipo por referência, seu objeto estará no heap. Apenas a referência fica na stack.
Resumo
- A stack cresce e desaparece automaticamente a cada chamada de função. É rápida porque é gerenciada apenas movendo um ponteiro.
- O heap reserva em tempo de execução o tamanho necessário. É flexível, mas a busca para alocação e o gerenciamento da desalocação têm custo.
- Responsabilidade pela desalocação: automática na stack e definida pela linguagem no heap (manual em C, GC em Java, ARC no Swift).
- Stack overflow é o erro que ocorre quando recursão profunda ou algo semelhante ultrapassa o limite da stack.
- No Swift, structs geralmente usam a stack e classes, o heap. Essa é a base de desempenho para priorizar tipos por valor.
- Porém, structs dentro de classes vivem no heap, e String e Array mantêm seus buffers internos lá.

