Conteúdos da cadeira

Ambientes de execução

Pilha, registos de ativação, organização de memória e passagem por valor contra passagem por referência.

Markdown

Perguntar sobre esta página

ChatGPTClaudePerplexityGeminiCopiar e abrir

Envia o link e pede à IA para ler a página. No Gemini, cola a pergunta copiada.

Ver pergunta para copiar
Nesta página

Quando o programa corre, cada chamada de função precisa de espaço próprio para parâmetros, variáveis locais e o endereço de retorno. Esse espaço é o registo de ativação (frame), e os registos empilham-se na pilha de execução: cada chamada empilha um registo, cada retorno desempilha-o. É a mesma pilha que viste na convenção de chamada, agora vista do lado de quem gera o código.

Organização de memória

Um programa em execução divide a memória em zonas com tempos de vida diferentes:

  • Código: as instruções, só de leitura.
  • Dados estáticos/globais: variáveis globais, com endereço fixo durante toda a execução.
  • Pilha (stack): registos de ativação, com ciclo de vida encaixado (o último a entrar é o primeiro a sair).
  • Monte (heap): objetos e vetores criados dinamicamente, com ciclo de vida livre e geridos pelo programador ou pelo coletor de lixo.

Variáveis locais vivem no registo de ativação da chamada atual, por isso duas chamadas da mesma função têm cópias independentes. É isto que torna a recursão possível: cada chamada tem o seu espaço, mesmo sendo o mesmo código.

Exemplo: recursão com dois registos

int fat(int n) {
  if (n <= 1) return 1;
  return n * fat(n - 1);
}

Chamada fat(2): empilha o registo 1 com n = 2 e o endereço de retorno. A condição é falsa, por isso chama fat(1): empilha o registo 2 com n = 1. Agora a condição é verdadeira e devolve 1; desempilha o registo 2. O registo 1 calcula 2 * 1 = 2, devolve e desempilha. Em cada instante, o n usado é o do registo do topo: nunca há confusão entre os dois n, porque vivem em endereços diferentes da pilha.

Passagem de parâmetros

  • Por valor: a função recebe uma cópia do argumento. Alterar o parâmetro dentro da função não afeta o chamador. É o comportamento dos tipos primitivos em Java e, com as regras próprias de cada linguagem, o caso comum que deves assumir por omissão. Em Programação viste os mesmos efeitos ao passar objetos contra primitivos.
  • Por referência: a função recebe acesso direto à variável do chamador (na prática, o endereço). Alterar o parâmetro altera o original. Útil para devolver vários resultados ou evitar copiar estruturas grandes, perigoso porque cria efeitos à distância.

A escolha aparece no código gerado: por valor copia o conteúdo para o registo do chamado; por referência copia só o endereço e cada acesso atravessa-o. Quando uma linguagem oferece os dois modos, a declaração do parâmetro decide qual o mecanismo, e a análise semântica já verificou a compatibilidade de tipos nessa altura.

Para levar para a próxima página

Sabemos onde cada valor vive em execução. O passo seguinte é representar o programa a meio caminho entre a árvore e a máquina: o código intermédio.

Ver o ficheiro no GitHub

À tua maneira

Escolhe como preferes ler.

Aparência
Ajustar cores e largura
Cor de destaque do tema FEUP
Tipo de letra

Álgebra, lógica e uma ideia de cada vez.

As tuas escolhas ficam guardadas neste navegador.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.