LRM Prof. Mantovani ← Aulas da disciplina
Semana 11 · Aula 11 de 14

Ambiente de execução

Como o programa traduzido se organiza na memória em tempo de execução: registros de ativação, a pilha de chamadas (e a recursão) e a distinção entre pilha e heap.

📚 Compiladores📝 mini-quiz ao final
Objetivos da aula

O que você vai aprender

1

Descrever o registro de ativação de uma função e seus campos.

2

Explicar a pilha de execução em chamadas e na recursão.

3

Distinguir alocação na pilha e no heap e seus riscos.

4

Relacionar a organização de memória às decisões do compilador.

1 · Motivação

Onde o programa "mora" enquanto roda

O compilador traduziu o código — mas para onde vão as variáveis locais, os parâmetros, o endereço de retorno quando o programa executa? O ambiente de execução (runtime) é a organização de memória que o compilador planeja para que tudo funcione.

Entender a pilha de execução explica recursão, estouro de pilha, vazamentos de memória e por que variáveis locais "somem" quando a função retorna.

💡
O compilador não só traduz instruções: ele decide o layout de memória que o programa usará em tempo de execução.
2 · Mapa

A memória de um processo

Um programa em execução divide sua memória em regiões:

Código
instruções
Dados
globais
Heap
cresce ↑
Pilha
cresce ↓

Pilha e heap crescem em sentidos opostos no espaço livre. O compilador organiza, sobretudo, a pilha — onde vivem as chamadas de função.

3 · Conceito

O registro de ativação

Registro de ativação (stack frame). Bloco de memória criado a cada chamada de função, contendo tudo o que aquela ativação precisa: parâmetros, variáveis locais, endereço de retorno, valor de retorno e elos (de controle e de acesso).

É a "ficha" daquela execução específica da função. Cada chamada tem o seu próprio registro, independente das demais.

4 · Explicação

O que cada campo guarda

Os campos típicos de um registro de ativação:

  • Parâmetros: os argumentos passados na chamada.
  • Variáveis locais: as declaradas dentro da função.
  • Endereço de retorno: onde continuar a execução após a função terminar.
  • Valor de retorno: espaço para devolver o resultado ao chamador.
  • Elo de controle: ponteiro para o registro do chamador (frame anterior).
  • Elo de acesso: ponteiro para o escopo léxico envolvente (em linguagens com funções aninhadas).
5 · Exemplo

Chamadas empilhando frames

A função recursiva fat(3) gera uma cadeia de chamadas, cada uma com seu frame:

// fat(3) chama fat(2) chama fat(1) fat(3) // frame de fat(3) no topo... por enquanto └ fat(2) // empilha frame de fat(2) └ fat(1) // empilha frame de fat(1) = caso base

O caso base fat(1) retorna primeiro; depois fat(2), depois fat(3) — a ordem inversa do empilhamento.

6 · Interativo

Evolução da pilha passo a passo

Passo 1
Chama fat(3): empilha frame de fat(3) com n=3. Ele precisa de fat(2).
Passo 2
Chama fat(2): empilha frame de fat(2) com n=2, sobre o de fat(3).
Passo 3
Chama fat(1): empilha frame de fat(1) com n=1. Caso base: retorna 1.
Passo 4
Desempilha fat(1). fat(2) calcula 2*1=2 e retorna; desempilha.
Passo 5
fat(3) calcula 3*2=6 e retorna; desempilha. Pilha vazia, resultado 6.
7 · Conceito

A pilha de execução

Pilha de execução (call stack). Estrutura LIFO que guarda os registros de ativação das chamadas em andamento. Ao chamar, empilha-se um frame; ao retornar, desempilha-se.

O ponteiro de pilha (stack pointer) marca o topo. Empilhar e desempilhar são apenas ajustes desse ponteiro — operações baratíssimas, por isso a pilha é tão eficiente.

8 · Explicação

Por que a recursão funciona

Cada chamada recursiva cria um frame independente, com sua própria cópia dos parâmetros e locais. Por isso fat(3) e fat(2) coexistem sem confundir seus valores de n.

💡
A recursão não precisa de mágica: é só a consequência natural de cada chamada ter seu próprio registro de ativação na pilha.
9 · Analogia

A pilha de pratos

🍽️ Analogia
A pilha de execução é como uma pilha de pratos: você só adiciona ou remove do topo. A última função chamada (prato no topo) é a primeira a retornar (LIFO). Cada prato é um registro de ativação. Se você empilha pratos sem parar — recursão infinita — a pilha desaba: é o stack overflow.
10 · Comparação

Pilha × heap

Pilha (stack)Heap
O que guardaLocais, parâmetros, retornoMemória dinâmica (new/malloc)
Tempo de vidaDa chamada ao retornoAté a liberação/coleta
GerênciaAutomática (LIFO)Manual ou coletor de lixo
VelocidadeMuito rápida (move ponteiro)Mais lenta (busca espaço livre)
Risco típicoStack overflowVazamento / fragmentação
11 · Fluxo

O protocolo de chamada

Chamada
empilha frame
Executa
usa locais
Retorna
valor + endereço
Desempilha
frame some

A divisão de tarefas entre chamador e chamado (quem empilha argumentos, quem limpa a pilha) é a convenção de chamada — fixada pelo compilador e a arquitetura.

12 · Aprofundamento

Convenção de chamada e ponteiro de quadro

Ponteiro de quadro (frame pointer). Registrador que aponta para uma posição fixa dentro do frame atual, servindo de referência para acessar parâmetros e locais por deslocamento (offset).

Enquanto o stack pointer se move conforme se empilha/desempilha, o frame pointer fica estável durante a execução da função, dando endereços previsíveis: local_x = frame_pointer - 8, por exemplo. É na tabela de símbolos (aula 9) que esses deslocamentos foram calculados.

13 · Interativo

Verifique seu entendimento

Por que cada chamada recursiva não atrapalha as outras?

Frames independentes por chamada garantem cópias separadas de parâmetros e locais.
14 · Caso prático

Local na pilha × objeto no heap

Em uma linguagem como C, compare:

void f() { int v[100]; // na PILHA: some ao retornar de f() int* p = malloc(400); // no HEAP: sobrevive até free(p) } // v desaparece; se faltar free(p), vaza memória

Retornar um ponteiro para v seria um erro grave: a memória já não existe após o retorno.

15 · Erros comuns

Perigos do ambiente de execução

⚠️
Stack overflow. Recursão sem caso base (ou profunda demais) esgota a pilha e o programa quebra.

Vazamento de memória. Alocar no heap e nunca liberar faz o consumo crescer indefinidamente.

Ponteiro pendurado (dangling). Usar memória da pilha após o retorno da função, ou do heap após o free.

Confundir pilha e heap. Achar que variáveis locais sobrevivem à função, ou que o heap se limpa sozinho sem coletor.
16 · Dicas

Convivendo bem com a memória

Garanta um caso base alcançável em toda recursão para não estourar a pilha.

Para cada alocação no heap, planeje a liberação (ou confie no coletor de lixo, se houver).

Nunca retorne ponteiro para variável local: ela morre com o frame.

Lembre que a pilha é rápida: prefira-a para dados de vida curta e tamanho conhecido.
17 · Interativo

Pense antes de revelar

O que aconteceria ao chamar fat(100000) numa recursão sem otimização de cauda?
Cada chamada empilha um novo registro de ativação antes de qualquer retorno acontecer (o caso base só é atingido no fundo). Para fat(100000), seriam 100000 frames empilhados simultaneamente. Como a pilha tem tamanho limitado, ela se esgota muito antes disso: ocorre stack overflow e o programa é abortado. A solução seria uma versão iterativa ou recursão de cauda otimizada, que reaproveita o mesmo frame.
18 · Flashcards

Revise os termos

Registro de ativaçãovirar
Frame criado por chamada: parâmetros, locais, endereço de retorno, elos.
Pilha de execuçãovirar
Estrutura LIFO dos frames das chamadas em andamento; empilha ao chamar, desempilha ao retornar.
Heapvirar
Região de memória dinâmica (new/malloc); vive até a liberação ou coleta de lixo.
Frame pointervirar
Registrador que referencia o frame atual; acessa locais/parâmetros por deslocamento fixo.
19 · Conexões

Como esta aula se liga ao curso

  • Usa a aula 9 (tabela de símbolos): os deslocamentos das variáveis no frame foram calculados ali.
  • Prepara a aula 12 (código): a geração de código emite as instruções que empilham/desempilham frames.
  • Liga-se à RI (aula 10): chamadas de função no TAC viram protocolos de pilha no código final.
  • Explica fenômenos práticos: stack overflow e vazamentos que o programador encontra no dia a dia.
20 · Síntese

O essencial da aula

🔑
Cada chamada cria um registro de ativação (parâmetros, locais, retorno, elos) na pilha de execução LIFO. A recursão funciona porque cada chamada tem seu frame independente. A pilha guarda dados de vida curta (automática); o heap guarda memória dinâmica (manual ou coletada). Estouro de pilha e vazamentos são os riscos clássicos.
Mão na massa · colaborativo

Atividade em grupo · Desenhando a pilha

Em duplas, desenhem a evolução da pilha em uma função recursiva.

⏱️ 20 min👥 duplas🧩 traço de execução

Roteiro

  1. Tomem fatorial(4) ou fibonacci(4).
  2. Desenhem a pilha de registros a cada chamada e a cada retorno.
  3. Marquem o valor retornado por cada frame.
  4. Discutam: o que aconteceria com a entrada 100000?
Empilhadordesenha as chamadas
Desempilhadordesenha os retornos
📤 Entrega: Diagrama da pilha em pelo menos 3 instantes da execução.
Teste seu conhecimento

Mini-quiz · Aula 11

20 questões sobre esta aula. Escolha e veja a explicação na hora.

0/20

📌 Resumo — leve isto para a prova

  • Cada chamada cria um registro de ativação na pilha (parâmetros, locais, retorno, elos).
  • A pilha é LIFO; a recursão usa um frame independente por chamada.
  • A pilha guarda dados de vida curta (automática); o heap guarda memória dinâmica (manual/coletada).
  • Estouro de pilha e vazamentos de memória são os riscos clássicos do runtime.
  • O frame pointer dá acesso estável a locais e parâmetros por deslocamento.