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 |
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) |