Ciência da computação

Stack vs Heap: onde as variáveis ficam (incluindo tipos por valor do Swift)

A stack é limpa junto com as chamadas de função, enquanto o heap oferece tamanhos e tempos de vida dinâmicos. Este texto resume o custo das duas áreas, a disposição real dos tipos por valor e por referência do Swift e suas diferenças em relação às estruturas de dados Stack e Heap.

5 min de leitura
Imagem de capa de Stack vs Heap: onde as variáveis ficam (incluindo tipos por valor do Swift)

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.

  1. Stack: área gerenciada automaticamente, que cresce com chamadas de função e desaparece no retorno. É rápida.
  2. Heap: área dinâmica usada em tempo de execução no tamanho necessário. É flexível, mas tem custo de gerenciamento.
  3. A essência da diferença de velocidade está em “como alocar e desalocar” e “quem faz a limpeza”.
  4. 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).

Uma referência do stack frame apontando para o objeto real no heap
Uma referência do stack frame apontando para o objeto real no heap

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.

A struct fica inteira na stack; a classe mantém o objeto no heap e apenas sua referência na stack
A struct fica inteira na stack; a classe mantém o objeto no heap e apenas sua referência 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á.