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

Tabela de símbolos e verificação de tipos

A memória do compilador: a tabela de símbolos com escopos aninhados e o sistema de tipos que detecta incompatibilidades antes da execução.

📚 Compiladores🧪 1 simulador(es)📝 mini-quiz ao final
Objetivos da aula

O que você vai aprender

1

Estruturar uma tabela de símbolos e descrever suas operações.

2

Modelar escopos aninhados como uma pilha de tabelas.

3

Explicar a verificação de tipos e os conceitos de coerção e sombreamento.

4

Relacionar escopo léxico ao aninhamento de blocos no código.

1 · Motivação

Quem é "x"?

Quando o compilador lê x = x + 1, precisa saber: qual x? De que tipo? Onde mora na memória? Essas respostas vivem na tabela de símbolos — a memória de longo prazo do compilador — e a checagem de compatibilidade vive no sistema de tipos.

Sem essas duas estruturas, o compilador seria incapaz de distinguir um nome válido de um erro, ou uma operação legítima de uma absurda como somar um número a uma função.

💡
A tabela de símbolos é preenchida nas declarações e consultada em cada uso. É a estrutura mais transversal do compilador.
2 · Mapa

Estruturas transversais

A tabela de símbolos não pertence a uma única fase: ela atravessa quase todas.

LéxicaSintáticaSemânticaGeração

A análise semântica é onde ela mais trabalha: preenche-a nas declarações, consulta-a nos usos e usa-a para verificar tipos. A geração de código volta a consultá-la para saber posições de memória.

3 · Conceito

O que é a tabela de símbolos

Tabela de símbolos. Estrutura de dados que associa cada identificador às suas informações: tipo, categoria (variável, função, parâmetro), escopo, posição de memória, e atributos específicos (assinatura de função, dimensão de vetor etc.).

Implementada quase sempre como tabela hash, por oferecer inserção e busca por nome em tempo praticamente constante — operações executadas milhares de vezes durante a compilação.

4 · Explicação

As operações essenciais

Toda tabela de símbolos oferece, no mínimo:

  • inserir(nome, info) — registra uma declaração nova no escopo atual.
  • buscar(nome) — localiza a declaração visível mais próxima, percorrendo escopos de dentro para fora.
  • abrirEscopo() / fecharEscopo() — empilha e desempilha escopos ao entrar e sair de blocos.
💡
A busca não para no primeiro escopo: se não achar localmente, ela "sobe" para o escopo envolvente, e assim por diante até o global.
5 · Exemplo

Escopos no código

O código a seguir mostra três escopos e um nome reutilizado:

int x; // escopo global void f() { int x; // sombreia o x global { int y; } // y só existe neste bloco interno // aqui y já não existe; x refere o local }

Há três x possíveis em jogo? Não: dentro de f, todo uso de x liga-se ao local; o global fica oculto (sombreado).

6 · Interativo

Resolução de nome passo a passo

Passo 1
O parser encontra o uso de x dentro do bloco interno de f().
Passo 2
buscar(x) começa no escopo mais interno (o bloco { }). Não há x declarado ali.
Passo 3
Sobe para o escopo da função f(). Encontra int x. Para a busca aqui.
Passo 4
O uso liga-se ao x local de f(). O x global nunca é consultado: foi sombreado.
Passo 5
Se nem f() nem o global tivessem x, a busca falharia: erro "identificador não declarado".
7 · Conceito

Escopo léxico e a pilha de tabelas

Escopo. Região do programa em que uma declaração é visível.
Escopo léxico (estático). A visibilidade é determinada pela estrutura textual do código (aninhamento de blocos), não pela ordem de execução.

Implementa-se como uma pilha de tabelas: ao entrar num bloco, empilha-se um novo escopo; ao sair, desempilha-se. A busca varre a pilha do topo (interno) para a base (global).

8 · Explicação

Por que a busca vai de dentro para fora

A regra do escopo mais interno diz que, havendo vários nomes iguais visíveis, vence o declarado mais perto do uso. Isso reflete a intuição do programador: a variável "mais local" é a que ele quis usar.

🪆 Analogia
Pense em bonecas russas (matrioscas). Você está dentro da boneca mais interna e procura um objeto: olha primeiro nela; se não achar, abre para a boneca que a envolve, e assim por diante até a maior. Nunca o contrário.
9 · Analogia

A tabela como agenda telefônica

📒 Analogia
A tabela de símbolos é uma agenda: o nome é a chave (hash), e a "ficha" guarda tudo sobre a pessoa (tipo, escopo, endereço). Quando há homônimos em contextos diferentes (um "João" do trabalho, outro da família), você consulta primeiro o contexto mais próximo. Sombreamento é exatamente ter dois "João" e usar, por padrão, o mais próximo de você.
10 · Comparação

Escopo estático × dinâmico

Escopo estático (léxico)Escopo dinâmico
Definido porEstrutura textual do códigoOrdem das chamadas em execução
ResolvidoEm tempo de compilaçãoEm tempo de execução
PrevisibilidadeAlta (lendo o código)Baixa (depende do caminho)
Usado porC, Java, Python, a maioriaLisp antigo, alguns shells

A esmagadora maioria das linguagens modernas usa escopo estático — daí o foco na pilha de tabelas resolvida na compilação.

11 · Fluxo

O ciclo de vida de um escopo

Entra no bloco
abrirEscopo
Declara nomes
inserir
Usa nomes
buscar
Sai do bloco
fecharEscopo

Ao fechar o escopo, suas declarações deixam de ser visíveis — é assim que y "desaparece" ao sair do bloco interno do exemplo.

12 · Aprofundamento

O sistema de tipos

Sistema de tipos. Conjunto de regras que atribui um tipo a cada expressão e define quais operações são válidas entre quais tipos.

O verificador de tipos percorre a árvore, sintetizando o tipo de cada subexpressão (um atributo, como na aula 8!) e comparando-o com o tipo esperado pelo contexto. Tipos incompatíveis geram um erro de tipo.

  • Tipagem estática: tipos verificados na compilação (C, Java).
  • Tipagem dinâmica: tipos verificados na execução (Python, JavaScript).
13 · Interativo

Verifique seu entendimento

Com escopos aninhados, em que ordem buscar(nome) procura a declaração?

A regra do escopo mais interno: procura-se primeiro no escopo atual e sobe-se gradualmente até o global.
14 · Caso prático

Caçando um erro de tipo

Considere o trecho com tipagem estática:

int n = 10; char* s = "oi"; int r = n + s; // ERRO: somar int com ponteiro/string

O verificador sintetiza tipo(n)=int e tipo(s)=ponteiro. A regra de + não admite essa combinação, então emite um erro de tipo — antes de o programa rodar e quebrar.

15 · Erros comuns

Onde o compilador (e o aluno) tropeça

⚠️
Esquecer de fechar o escopo. Deixar declarações de um bloco "vazarem" para fora corrompe a resolução de nomes.

Confundir sombreamento com erro. Reusar um nome em escopo interno é legal (sombreia); não é redeclaração ilegal.

Coerção silenciosa perigosa. Conversões implícitas (int→float, int→char) podem mascarar bugs. Nem todo "funciona" é "correto".

Buscar de fora para dentro. Inverter o sentido da busca leva a ligar usos às declarações erradas.
16 · Dicas

Boas práticas de projeto

Use hash para a tabela: inserção e busca rápidas são decisivas no desempenho do compilador.

Modele escopos como pilha: abrir/fechar são apenas push/pop, e a busca é uma varredura natural.

Guarde na ficha tudo que fases posteriores vão precisar: tipo, posição de memória, assinatura.

Trate coerção explicitamente: defina onde ela é permitida em vez de deixá-la implícita e imprevisível.
17 · Interativo

Pense antes de revelar

No exemplo "int x; void f(){ int x; { int y; } }", quantos identificadores distintos existem e quais escopos são fechados?
Existem três identificadores distintos: o x global, o x local de f() (que sombreia o global dentro de f) e o y do bloco interno. Dois escopos são abertos e depois fechados: o da função f() e o do bloco { } interno. Ao fechar o bloco interno, y deixa de existir; ao fechar f(), o x local desaparece e só o x global permanece.
18 · Flashcards

Revise os termos

Tabela de símbolosvirar
Hash que associa nome → (tipo, escopo, posição, atributos). Preenchida nas declarações, consultada nos usos.
Escopo léxicovirar
Visibilidade determinada pela estrutura textual (aninhamento), resolvida na compilação.
Sombreamentovirar
Declaração interna de mesmo nome oculta a externa naquele escopo; resolve-se pelo escopo mais interno.
Coerçãovirar
Conversão implícita de tipo controlada pelo compilador (ex.: int para float).
19 · Conexões

Como esta aula se liga ao curso

  • Vem da aula 8 (atributos): o tipo de cada expressão é um atributo sintetizado; inserir é uma ação semântica.
  • Usa a aula 2 (Chomsky): "declarar antes de usar" e checagem de tipos são sensíveis ao contexto, fora da GLC.
  • Prepara a aula 11 (execução): a posição de memória guardada na ficha vira o deslocamento no registro de ativação.
  • Prepara a aula 12 (código): a geração consulta a tabela para localizar variáveis.
20 · Síntese

O essencial da aula

🔑
A tabela de símbolos (geralmente um hash) guarda nome → (tipo, escopo, posição). Escopos aninhados formam uma pilha, e a busca vai do mais interno ao mais externo — daí o sombreamento. A verificação de tipos sintetiza o tipo de cada expressão e barra operações incompatíveis, com coerção controlada quando permitida.
Mão na massa · colaborativo

Atividade em grupo · Detetives de escopo

Em duplas, resolvam a quem cada uso de variável se refere.

⏱️ 20 min👥 duplas🧩 análise de código

Roteiro

  1. Recebam um trecho com blocos aninhados e variáveis de mesmo nome.
  2. Para cada uso, indiquem qual declaração ele referencia (qual escopo).
  3. Apontem possíveis erros de tipo no mesmo trecho.
  4. Justifiquem usando a regra do escopo mais interno.
Rastreador de escopoliga uso↔declaração
Auditor de tiposcaça incompatibilidades
📤 Entrega: Trecho anotado com as ligações de escopo e os erros de tipo encontrados.
Teste seu conhecimento

Mini-quiz · Aula 9

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

0/20

📌 Resumo — leve isto para a prova

  • A tabela de símbolos (hash) guarda nome → (tipo, escopo, posição, atributos).
  • Escopos aninhados formam uma pilha; a busca vai do interno ao externo.
  • Sombreamento é resolvido pela regra do escopo mais interno.
  • A verificação de tipos sintetiza tipos e barra operações incompatíveis.
  • Escopo léxico é resolvido na compilação pela estrutura textual do código.