Yasir Explains/Compiler Design/Intermediate Code Generation/Three-Address Statements
Intermediate Code Generation

Three-Address Statements

On this page

DefinitionWhere Temporaries Come FromThe Common FormsWorked Example: x = (a + b) \* (c - d)How They Are StoredKey Points
Intermediate Code Generation

Three-Address Statements

Three-address code: a linear intermediate representation where every instruction has at most one operator and three addresses. Covers the general form, where temporaries come from, the common statement forms, and a fully worked translation of (a + b) * (c - d).

Definition

A three-address statement (or instruction) is an intermediate-code instruction with at most one operator on the right-hand side and at most three addresses — two operands and one result — of the general form x = y op z.

An address is one of three things:

  • a name — a programmer variable such as a or count;
  • a constant — a literal such as 4 or 3.14;
  • a compiler-generated temporary — a fresh name such as t1, invented to hold an intermediate result.

Why "three-address"?

Look at the general form x = y op z. It mentions three addresses:

Example
x = y op z
▲ ▲ ▲
result operand operand

The result x is one address; the two operands y and z are the other two. Because at most one operator is allowed per statement, a complex expression cannot be written in a single line — it must be flattened into a sequence of these simple statements, each introducing a temporary for one operator. That uniformity is exactly what makes three-address code easy to optimise and easy to translate to a real machine.

Where Temporaries Come From

A complex expression is broken into a sequence of three-address statements, generating one new temporary per interior (operator) node of its syntax tree. Each operator in the tree becomes exactly one statement, and the temporary that statement assigns to is the "result" handed up to the parent node.

Take the running example x = (a + b) \* (c - d). Its syntax tree has three interior nodes — +, -, and \* — so we generate three temporaries t1, t2, t3, plus a final copy into x:

Example
Syntax tree Three-address code
----------------------------
= t1 = a + b ← the (+) node
/ \ t2 = c - d ← the (-) node
x \* t3 = t1 \* t2 ← the (\*) node
/ \ x = t3 ← the (=) node
+ -
/| |\
a b c d
=
x
*
+
a
b
-
c
d

The leaves (a, b, c, d, x) are programmer variables and need no temporaries — only the interior operator nodes do. Translation walks the tree bottom-up: each child is reduced to a single address before its parent is emitted, which is why t3's operands are already the temporaries t1 and t2.

The Common Forms

Despite the single name, "three-address statement" covers a small family of instruction shapes. Each still obeys the at-most-one-operator rule.

FormSyntaxExampleMeaning
Binary assignmentx = y op zt1 = a + bapply a binary operator
Unary assignmentx = op yt = minus c , x = not yapply a unary operator
Copyx = yx = t3copy one address into another
Unconditional jumpgoto Lgoto L1jump to labelled statement L
Conditional jumpif x relop y goto Lif a < b goto L1jump to L when the test holds
Procedure callparam x … call p, n … return yparam a ; call p, 1pass n params, then call p
Indexed copyx = y[i] , x[i] = yt = a[i]read / write an array element
Pointer & addressx = &y , x = \*y , \*x = yp = &atake address / load / store via pointer

A few notes:

  • Unary operators like minus (arithmetic negation) and not (logical) take only one operand, so they fit in x = op y.
  • Jumps carry a label L rather than a data operand; labels mark target statements and are how control flow is expressed in flat code.
  • A procedure call is split across several statements: one param per argument, then a single call p, n naming the procedure and its argument count.

Worked Example: x = (a + b) \* (c - d)

Walking the syntax tree bottom-up, each interior node produces one statement:

Example
t1 = a + b // the + node: combine a and b, hold in t1
t2 = c - d // the - node: combine c and d, hold in t2
t3 = t1 \* t2 // the \* node: multiply the two subresults
x = t3 // the = node: copy the final result into x

Reading it line by line:

  1. t1 = a + b — the left subtree (a + b) is reduced to the single address t1.
  2. t2 = c - d — the right subtree (c - d) is reduced to the single address t2.
  3. t3 = t1 \* t2 — the root multiply combines the two subresults; both operands are already single addresses, satisfying the at-most-three-addresses rule.
  4. x = t3 — a copy statement stores the result into the programmer's variable x.

Three operators in the source produced three temporaries (t1, t2, t3) — one per interior node — plus one copy.

How They Are Stored

These statements are not kept as text; they are held in one of three concrete data structures — quadruples, triples, and indirect triples — each a different way of recording the operator and its addresses, covered in the next topic.

Key Points

  • A three-address statement has at most one operator and at most three addresses (two operands, one result): x = y op z.
  • An address is a name, a constant, or a compiler-generated temporary.
  • A complex expression is flattened into a sequence of these statements — one temporary per interior node of the syntax tree.
  • The family of forms includes binary/unary assignments, copies, jumps, conditional jumps, calls, indexed accesses, and pointer operations.
  • For x = (a + b) \* (c - d) the three operators yield t1, t2, t3 plus a copy into x.
  • Three-address code is stored as quadruples, triples, or indirect triples.