Introdução à Compilação
Aula 10 – Revisão Primeira Prova Prof. Nemésio Freitas Duarte FilhoCompiladores
Um compilador é um programa que lê um programa
escrito numa linguagem – a linguagem fonte –
e o traduz num programa equivalente em outra
linguagem – a linguagem alvo.
Como importante etapa deste processo de
tradução, o compilador relata a seu usuário a
presença de erros no programa fonte.
Compiladores
Uma linguagem alvo pode ser uma outra
linguagem de programação ou a linguagem de
máquina de uma família de computadores.
Os compiladores podem ser classificados como de
uma passagem (um passo) ou de passagens
múltiplas (n passos).
O compilador de um passo pode ser representado
pela figura do slide anterior. Neste caso o
programa alvo ou objeto já é o produto final do
compilador, ou seja, o programa em linguagem de
máquina desejado.
Compiladores
No caso de n passos teremos programas objeto intermediários
que serão o programa fonte do próximo passo.
No próximo passo, novo programa objeto é gerado e assim
sucessivamente.
Os programas objeto gerados nos passos intermediários são ditos
Compiladores
Vantagens do Compilador de vários passos:
Menor utilização da memória do computador, já que cada passo
exerce apenas uma parte das funções de todo o compilador. Muitas vezes esta vantagem pode se tornar irrelevante devido ao fato do hardware estar se tornando mais farto a cada geração de
computadores.
Maior possibilidade de se efetuar otimizações levando a um programa objeto mais eficiente quanto ao espaço ocupado e ao tempo de
processamento. Grandes avanços de arquitetura e técnicas de processamento como técnicas RISC e uso de pipeline em
processadores se devem em parte pela evolução dos compiladores. Os projetos e implementações das várias partes do compilador são
Compiladores
Desvantagens:
Maior volume de entrada/saída (tipicamente acesso a disco ou à rede) quando os programas intermediários e os passos do
compilador não ficam residentes em memória (o que é comum). Em geral há aumento do tempo de compilação.
Aumento do projeto total, com necessidade de introdução das linguagens intermediárias.
Compilação X Interpretação
Já tendo sido mencionado o conceito básico de
compilação vamos mencionar outra forma de
executarmos nossos programas.
A Interpretação é o processo de execução em
que cada instrução é traduzida e executada em
seguida.
O resultado desta tradução não é armazenado ou
gravado em lugar algum e é utilizado apenas no
instante da execução da referida instrução.
Compilação X Interpretação
Existe uma análise do comando e posterior tradução para a
linguagem de máquina que são etapas análogas às
executadas pelo compilador.
A tradução propriamente dita pode não ser executada e sim
ocorrer a chamada de um subprograma em linguagem de
máquina que execute os comandos fruto da análise.
A diferença é que o compilador atua sobre todo o programa
fonte gerando um programa objeto e o interpretador
executa cada comando e não gera um objeto após a
execução.
Em nova execução o interpretador executará todas as
Compilação X Interpretação
Os programas compilados em geral apresentam maior
desempenho e introduzem otimizações no código gerado que não podem ser implementadas por interpretadores, entretanto, se houver qualquer alteração no código fonte nova compilação é necessária.
Note que introduzimos aqui o termo código como sinônimo de
programa.
Existem situações em que parte do código é compilado para
um executável e outra parte interpretada no momento da execução.
A técnica de interpretação passou a se justificar muito mais
com o aumento da interconexão de computadores em rede e com a distribuição das aplicações.
O Modelo de compilação de Análise e
Síntese
Existem duas partes na compilação: a análise e a síntese.
A parte de análise divide o programa fonte nas partes constituintes
e cria uma representação intermediária do mesmo .
Durante a análise o compilador deve ser capaz de detectar se o
programa é inválido por qualquer razão, isto é, não corresponde em algum trecho à linguagem para a qual o compilador foi escrito.
Neste caso é gerada uma mensagem de erro. Imagine que, ao
escrever o seu programa em linguagem C, você introduziu por
distração um comando da linguagem Pascal que não existe em C.
Durante o processo de compilação você receberá uma mensagem
O Modelo de compilação de Análise e
Síntese
A síntese constrói o programa alvo desejado, a
partir da representação intermediária.
Durante a análise, as operações e comandos do
programa fonte são determinadas (identificadas)
e registradas numa estrutura hierárquica
chamada árvore.
Em geral é utilizado um tipo especial chamado
árvore sintática ou árvore de parse, na qual
cada nó representa uma operação e o filho de um
nó representa o argumento da operação.
O Modelo de compilação de Análise e
Síntese
Exemplo 1) Considere o trecho de programa em Pascal e
sua árvore sintática em seguida:
begin
x := 3 ;
write (x) ;
end;
O analisador constrói a árvore sintática e o sintetizador
percorre a árvore, visitando todos os nós, segundo uma
ordem pré definida, para produzir o código de máquina.
O Modelo de compilação de Análise e
Síntese
Exemplo 2) Considere o trecho de programa em Pascal: e
sua árvore sintática em seguida:
O Contexto de um compilador
Adicionalmente ao compilador, vários outros
programas podem ser necessários para se criar um
programa alvo executável.
Um programa fonte pode ser dividido em módulos
armazenados em arquivos separados.
A tarefa de coletar esses módulos é, algumas vezes,
confiada a um programa distinto chamado pré
processador.
O pré processador pode também expandir formas
curtas, chamadas macros, em enunciados da
linguagem fonte.
O Contexto de um compilador
A figura abaixo mostra uma
compilação típica.
O programa alvo criado pelo
compilador pode exigir
processamento posterior antes que possa ser executado.
O compilador da figura abaixo cria
um código de montagem (assembly) que é traduzido no de máquina por um montador (assembler) e, então, ligado a algumas rotinas de
biblioteca (ligação ou “link-edição”), formando o código que é
efetivamente executado em máquina.
As fases de um compilador
Na prática algumas fases podem ser agrupadas
Análise do programa fonte
•Análise Linear: Busca cada caracter e identifica os
tokens.
• Tokens: Seqüência de caracteres com um significado
coletivo.
• Análise hierárquica: Agrupamento dos tokens em
coleções de mesmo significado.
• Análise semântica: Verifica se os componentes do
programa se combinam de forma significativa.
Análise do programa fonte
Análise léxica, linear, esquadrinhamento (scanning): o fluxo de
caracteres que constitui o programa é lido da esquerda para a direita e agrupado em tokens, que são seqüências de caracteres tendo um significado coletivo.
Exemplo:
montante := deposito_inicial + taxa_de_juros * 60
Os caracteres do enunciado de atribuição poderiam ser agrupados nos seguintes tokens: Identificador montante Símbolo de atribuição := Identificador deposito_inicial Sinal de adição + Identificador taxa_de_juros Sinal de multiplicação * Número 60
Os espaços que separam os tokens seriam normalmente eliminados durante a análise léxica.
Análise do programa fonte
Análise sintática, hierárquica ou gramatical: os caracteres ou tokens são
agrupados hierarquicamente em coleções aninhadas com significado coletivo. Em outras palavras agrupam-se os tokens em frases gramaticais. Estas frases são usadas pelo compilador para sintetizar uma saída. As frases gramaticais são representadas por árvores
Análise do programa fonte
Observe que taxa_de_juros * 60 forma uma unidade lógica e é separada
do termo anterior deposito_inicial pois sabemos que a multiplicação deve ser executada antes.
Veja algumas regras para a definição de expressões:
Qualquer identificador é uma expressão. Qualquer número é uma expressão.
Se X e Y são expressões então X+Y é uma expressão, X*Y é uma
expressão e (X) também é uma expressão.
Se Z é um identificador e X uma expressão, então Z:=X é um
enunciado.
Se X é uma expressão e C um comando, então while (X) do C e if (X)
then C são enunciados.
Logo, da figura anterior temos:
deposito_inicial, taxa_de_juros e 60 são expressões taxa_de_juros * 60 é uma expressão
Análise do programa fonte
Análise semântica: verificações são realizadas para assegurar que
componentes de um programa se combinam de forma significativa.
Esta fase verifica erros semânticos no programa fonte e captura
informações de tipo para a fase subseqüente de geração de código. Utiliza a estrutura hierárquica determinada pela fase de análise
sintática, a fim de identificar os operadores e operandos das expressões e enunciados.
Nesta etapa é importante a verificação de tipos que é realizada.
Esta verificação faz uma consistência das atribuições de operandos a operadores. Por exemplo: seja X um número real e VET um vetor, neste caso o compilador vai relatar um erro se o programa fonte contiver a expressão VET[X].
Vale lembrar que a representação interna do computador para um
Análise do programa fonte
Na árvore sintática da figura anterior, se supusermos que todos os
identificadores foram definidos (ou declarados) como reais e que o número 60 é um inteiro. A verificação de tipos revelará que a
multiplicação (*) se aplica a um real e um inteiro.
O enfoque geral é o de converter o inteiro em real antes de se efetuar a
Síntese do programa fonte
Gerenciamento da tabela de símbolos:
Uma função essencial do compilador é registrar os identificadores
usados no programa fonte e coletar as informações sobre os seus diversos atributos.
Esses atributos podem providenciar informações sobre a
memória reservada para o identificador, seu tipo, escopo (onde é válido no programa) e, no caso de nomes de procedimentos,
coisas tais como o número e os tipos de seus argumentos, o
método de transmissão de cada um (por exemplo, por referência) e o tipo retornado, se algum.
Uma tabela de símbolos é uma estrutura de dados contendo
um registro para cada identificador, com campos contendo os atributos do identificador.
Síntese do programa fonte
Gerenciamento da tabela de símbolos:
O analisador léxico detecta um identificador e o instala na tabela
de símbolos. As fases seguintes colocam informações sobre os identificadores (atributos) nesta tabela e em seguida as usam de várias maneiras.
Exemplo:
Análise semântica e geração do código intermediário
identificação dos tipos dos identificadores, verificação se o uso é válido, geração de operações apropriadas.
Gerador de código instala e usa informações detalhadas a
Síntese do programa fonte
Detecção de erros e geração de relatórios
Quando erros são encontrados, a compilação não para. Outros erros podem existir mais adiante.
Exemplos de erros:
Fase de análise léxica: Caracteres de entrada diferentes de qualquer token válido da linguagem.
Fase de análise sintática: Fluxo de tokens viola regras estruturais (sintaxe) da linguagem.
Na fase de análise semântica, o compilador detecta construções que possuam estrutura sintática correta, sem preocupação com o significado das operações. Por exemplo, não trata VETOR[i] +
Etapas pré e pós compilação
Pré processador
O pré processador permite que alguns recursos extras sejam introduzidos no código fonte, são eles:
definição de macros que são abreviações de construções mais longas. Inclusão de arquivos: Exemplo em C: # include <global.h> o pré
processador insere o arquivo global.h no lugar da diretiva. Pré processadores racionais: Permitem a inclusão de macros que
implementem construções novas que não existiam na versão anterior da linguagem. Exemplo: case expandido para if aninhados. Desta forma eles expandem linguagens mais antigas com facilidades mais modernas.
Extensores de linguagem: Pode conferir mais poder às linguagens através de macros embutidas. Exemplo: ## em C caracteriza a chamada a
procedimentos de acesso a Banco de dados que não fazem parte do C padrão.
Etapas pré e pós compilação
Montadores:
Alguns compiladores produzem um código de montagem. Este
código é repassado a um montador para processamento posterior. Outros compiladores realizam a tarefa do montador, produzindo um código de máquina relocável, que pode ser passado diretamente para um carregador/editor de ligações.
O código de montagem é uma versão mnemônica do código de
máquina, na qual são usados nomes em lugar do código binário para as operações e fornecidos nomes aos endereços de memória. Uma seqüência típica de instruções de montagem seria:
MOV a , R1 ADD #2 , R1 MOV R1 , b
Este código copia o conteúdo do endereço a no registrador R1,
adiciona a constante 2 ao mesmo, tratando o conteúdo do registrador R1 como um número em ponto fixo, e, finalmente armazena o resultado na localização denominada b. Computa, então, b := a + 2.
Exercícios
1. Em que situações é necessário construir um novo compilador ?
2. Qual a diferença entre compilação e interpretação?
3. No contexto de compiladores, por que Java é considerada uma linguagem
híbrida?
4. Em relação a passos de compilação, como são classificados os
compiladores?
5. Considere a linguagem de programação Pascal e dê pelo menos um
exemplo para cada tipo de erro: léxico, sintático e semântico.
6. Defina token
7. Qual(is) a(s) função(ões) do analisador léxico, sintático e semântico?
8. Explique as etapas que constituem o processo de compilação e seu
Dúvidas
E-mail:
nemesiofreitas@gmail.com
Site: