next up previous contents
Next: Notação posfixa Up: Geração de código Previous: Código intermediário   Sumário

Código de três endereços

O código de três endereços é composto por uma seqüência de instruções envolvendo operações binárias ou unárias e uma atribuição. O nome ``três endereços'' está associado à especificação, em uma instrução, de no máximo três variáveis: duas para os operadores binários e uma para o resultado. Assim, expressões envolvendo diversas operações são decompostas nesse código em uma série de instruções, eventualmente com a utilização de variáveis temporárias introduzidas na tradução. Dessa forma, obtém-se um código mais próximo da estrutura da linguagem assembly (Seção 4.1.1) e, conseqüentemente, de mais fácil conversão para a linguagem-alvo.

Uma possível especificação de uma linguagem de três endereços envolve quatro tipos básicos de instruções: expressões com atribuição, desvios, invocação de rotinas e acesso indexado e indireto.

Instruções de atribuição são aquelas nas quais o resultado de uma operação é armazenado na variável especificada à esquerda do operador de atribuição, aqui denotado por $ :=$. Há três formas para esse tipo de instrução. Na primeira, a variável recebe o resultado de uma operação binária:

     x := y op z
O resultado pode ser também obtido a partir da aplicação de um operador unário:
     x := op y
Na terceira forma, pode ocorrer uma simples cópia de valores de uma variável para outra:
     x := y

Por exemplo, a expressão em C

 a = b + c * d;
seria traduzida nesse formato para as instruções:
     _t1 := c * d
       a := b + _t1

As instruções de desvio podem assumir duas formas básicas. Uma instrução de desvio incondicional tem o formato

     goto L
onde L é um rótulo simbólico que identifica uma linha do código. A outra forma de desvio é o desvio condicional, com o formato
     if x opr y goto L
onde opr é um operador relacional de comparação e L é o rótulo da linha que deve ser executada se o resultado da aplicação do operador relacional for verdadeiro; caso contrário, a linha seguinte é executada.

Por exemplo, a seguinte iteração em C

 while (i++ <= k) 
   x[i] = 0;
 x[0] = 0;
poderia ser traduzida para
_L1: if i > k goto _L2
     i := i + 1
     x[i] := 0 
     goto _L1
_L2: x[0] := 0

A invocação de rotinas ocorre em duas etapas. Inicialmente, os argumentos do procedimento são ``registrados'' com a instrução param; após a definição dos argumentos, a instrução call completa a invocação da rotina. A instrução return indica o fim de execução de uma rotina. Opcionalmente, esta instrução pode especificar um valor de retorno, que pode ser atribuído na linguagem intermediária a uma variável como resultado de call.

Por exemplo, considere a chamada de uma função f que recebe três argumentos e retorna um valor:

 f(a, b, c);
Neste exemplo em C, esse valor de retorno não é utilizado. De qualquer modo, a expressão acima seria traduzida para
     param a
     param b
     param c
     _t1 := call f,3
onde o número após a vírgula indica o número de argumentos utilizados pelo procedimento f. Com o uso desse argumento adicional é possível expressar sem dificuldades as chamadas aninhadas de procedimentos.

O último tipo de instrução para códigos de três endereços refere-se aos modos de endereçamento indexado e indireto. Para atribuições indexadas, as duas formas básicas são

     x := y[i]
     x[i] := y
As atribuições associadas ao modo indireto permitem a manipulação de endereços e seus conteúdos. As instruções em formato intermediário também utilizam um formato próximo àquele da linguagem C:
     x := &y
     w := *x
     *x := z

A representação interna das instruções em códigos de três endereços dá-se na forma de armazenamento em tabelas com quatro ou três colunas. Na abordagem que utiliza quádruplas (as tabelas com quatro colunas), cada instrução é representada por uma linha na tabela com a especificação do operador, do primeiro argumento, do segundo argumento e do resultado. Por exemplo, a tradução da expressão a=b+c*d; resultaria no seguinte trecho da tabela:

  operador arg 1 arg 2 resultado
1 * c d _t1
2 + b _t1 a
Para algumas instruções, como aquelas envolvendo operadores unários ou desvio incondicional, algumas das colunas estariam vazias.

Na outra forma de representação, por triplas, evita a necessidade de manter nomes de variáveis temporárias ao fazer referência às linhas da própria tabela no lugar dos argumentos. Nesse caso, apenas três colunas são necessárias, uma vez que o resultado está sempre implicitamente associado à linha da tabela. No mesmo exemplo apresentado para a representação interna por quádruplas, a representação por triplas seria

  operador arg 1 arg 2
1 * c d
2 + b (1)


next up previous contents
Next: Notação posfixa Up: Geração de código Previous: Código intermediário   Sumário
Ivan L. M. Ricarte 2003-02-14