three-address code
/ TAC /
When you do long arithmetic in your head, you naturally break it into one tiny step at a time and jot down intermediate results: 'first a plus b, call it t; then t times c'. Three-address code is exactly that discipline written down as a compiler IR. Every instruction does at most one operation and names its result.
The name comes from the shape: each instruction has at most three 'addresses' — a destination and up to two source operands — in the form result = operand1 op operand2. So a single source expression like x = (a + b) * (c - d) is broken into a flat list: t1 = a + b; t2 = c - d; x = t1 * t2, inventing temporary names (t1, t2) for the in-between values. Control flow becomes explicit too, with simple conditional and unconditional jumps instead of nested if/while. There are no compound expressions hidden inside one instruction, which is precisely what makes it easy to analyze and transform.
It matters because this flattening is what later analyses and optimizations build on: once every operation is a single named step, the compiler can ask clean questions like 'is t1 still needed?' or 'is this value the same as one computed earlier?'. Three-address code is also the natural stepping stone toward static single assignment form. A caveat: three-address code is a style, not one fixed language — real compilers add their own conventions, and the 'three addresses' is a loose name (some instructions like a call or a jump have a different shape).
// source: // three-address code: x = (a + b) * (c - d); t1 = a + b t2 = c - d x = t1 * t2
One result, at most two operands, one operation per line — temporaries hold the in-between values.
Three-address code is a family of formats, not a single standard language; the 'three addresses' rule of thumb does not apply uniformly (calls, jumps, and array accesses take other shapes).