Code à trois adresses

Un article de Wikipédia, l'encyclopédie libre.

En informatique, le code à trois adresses[1] (TAC ou 3AC) est un type de langage intermédiaire utilisé par les compilateurs comme Clang/LLVM. Chaque instruction TAC a au plus trois opérandes et est généralement une combinaison d'affectation et d'un opérateur binaire. Par exemple : t1 := t2 + t3. Le nom dérive de l'utilisation de trois opérandes dans ces instructions même si des instructions avec moins d'opérandes peuvent se produire.

Étant donné que le code à trois adresses est utilisé comme langage intermédiaire dans les compilateurs, les opérandes ne seront probablement pas des adresses mémoire concrètes ou des registres de processeur, mais plutôt des adresses symboliques qui seront traduites en adresses réelles lors de l'allocation des registres. Il n'est pas rare non plus que les noms d'opérandes soient numérotés séquentiellement, car le code à trois adresses est généralement généré par le compilateur.

Exemples[modifier | modifier le code]

Calcul d'une solution d'une équation du second degré[modifier | modifier le code]

En code à trois adresses, cela se décompose en plusieurs instructions distinctes, plus faciles à traduire en langage d'assemblage. Il est également plus facile de détecter des sous-expressions qui se répètent, pour raccourcir le code.

Instruction à convertir :

Écriture dans un langage de programmation classique :

x = (-b + sqrt(b^2 - 4*a*c)) / (2*a)

Code à trois adresses :

t1 := b * b
t2 := 4 * a
t3 := t2 * c
t4 := t1 - t3
t5 := sqrt(t4)
t6 := 0 - b
t7 := t5 + t6
t8 := 2 * a
t9 := t7 / t8
x := t9

Boucle[modifier | modifier le code]

Le code à trois adresses peut avoir des sauts conditionnels et inconditionnels et des méthodes d'accès à la mémoire. Il peut également avoir des méthodes d'appel de fonctions. De cette façon, le code à trois adresses peut être utile dans l'analyse de flux de contrôle. Dans l'exemple de type C ci-après, une boucle stocke les carrés des nombres entre 0 et 9 :

int b[10];
for (int i = 0; i < 10; ++i) {
    b[i] = i * i; 
}
     t1 := 0               ; Initialisation de i
     t2 := b               ; Initialisation d'un pointeur sur b[i]
L1:  if t1 >= 10 goto L2   ; Saut conditionnel
     t3 := t1 * t1         ; Calcul du carré de i
     *t2 := t3             ; Déréférencement, affectation dans b[i]
     t1 := t1 + 1          ; Incrémentation de i
     t2 := t2 + 4          ; Passage à la case b[i] suivante
     goto L1               ; Répétition de la boucle
L2:

Le code à trois adresses sur la droite contient une optimisation : le pointeur t2, qui parcourt les cases du tableau b, est incrémenté en synchronisation avec i. Le chiffre 4 correspond au nombre d'octets utilisé par chaque case de b (en C, une variable de type int occupe généralement 4 octets). Sans cette optimisation, il aurait été nécessaire d’utiliser deux adresses (appelées t4 et t5 ci-dessous) à la place de la simple adresse t2 :

t4 := i * 4
t5 := b + t4
*t5 := t3

Voir aussi[modifier | modifier le code]

Notes et références[modifier | modifier le code]

  1. Aho, Alfred V. (Sethi, Ravi., Ullman, Jeffrey D., 1942-), Compilers, principles, techniques, and tools, Reading, Mass., Addison-Wesley Pub. Co, , 466 (ISBN 0201100886, OCLC 12285707, lire en ligne Inscription nécessaire)