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 zO resultado pode ser também obtido a partir da aplicação de um operador unário:
x := op yNa 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 Londe 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 Londe 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,3onde 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] := yAs 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 |
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) |