Code That Does Nothing

Not every line you write contributes to the program's result. Sometimes a computed value is never used. Sometimes a branch can never be taken. This is dead code, and a good optimizer simply removes it. The result you observe is unchanged, but the instructions that produced unused or unreachable work are gone.

There are two flavours, and this lesson covers both:

  • Unused results: a value is computed but never read. Eliminating it is sometimes called dead store elimination.
  • Unreachable code: a branch the compiler can prove will never execute.

A Function With Both Kinds

Here is a function that contains some genuinely useful work surrounded by code that does nothing:

int compute(int x)
{
    int unused{x * 99};
    int result{x + 1};
    if (false)
    {
        result = result * 1000;
    }
    return result;
}

Read it as the compiler must. unused is computed but never read by anyone; the function returns result, not unused. And the if (false) block can never run, so the * 1000 inside it is unreachable. The only line that actually affects the return value is result = x + 1.

At -O0, the compiler does not reason about any of this. It computes everything you wrote:

compute(int):
	push	rbp
	mov	rbp, rsp
	mov	DWORD PTR -20[rbp], edi
	mov	eax, DWORD PTR -20[rbp]
	imul	eax, eax, 99
	mov	DWORD PTR -4[rbp], eax
	mov	eax, DWORD PTR -20[rbp]
	add	eax, 1
	mov	DWORD PTR -8[rbp], eax
	mov	eax, DWORD PTR -8[rbp]
	pop	rbp
	ret

There it is: imul eax, eax, 99 faithfully computes x * 99 and stores it into the unused slot at -4[rbp], even though nothing will ever read it. (The if (false) block produced no instructions even at -O0, because the compiler can drop a literally-constant-false branch trivially. The wasted multiply, however, is fully present.)

The Optimized Version

Now -O2:

compute(int):
	lea	eax, 1[rdi]
	ret

Two instructions. lea eax, 1[rdi] computes x + 1 (the address-calculation form again, here just adding 1 to rdi) and leaves it in eax, ready to return. That is the whole function.

The x * 99 multiplication is completely gone. The optimizer traced the data flow, saw that unused is written and never read, and removed both the computation and the store. There was no point computing a number nobody would ever look at. The unreachable if (false) branch, naturally, contributes nothing either. What remains is exactly the work that influences the return value, and not one instruction more.

"But I Wrote That Code"

This is the part that surprises people the first time they see it: you wrote int unused{x * 99};, and in the optimized binary that multiplication does not exist. This is not the compiler ignoring your code or making a mistake. It is the compiler honouring the as-if rule: it may do anything it likes internally, as long as the program's observable behavior is as if it had executed your code exactly. Since the result of x * 99 is never observed, computing it or not makes no observable difference, so the optimizer is free to skip it.

This is also why dead code elimination is so tightly linked to the other optimizations in this chapter. Inlining and constant propagation constantly create dead code. When a constant gets folded, the variables that fed it become unused. When a function is inlined and its result is partly ignored, the ignored part becomes dead. Dead code elimination is the cleanup crew that sweeps away whatever the other passes leave behind.

A Caution About "Unused"

Be careful with the intuition that "the compiler removes anything unused". It removes work whose result is unobserved and whose computation has no side effects. A function call that prints to std::cout, writes to a file, or modifies a global has observable side effects, so even if you ignore its return value, the call stays. The optimizer only deletes work it can prove changes nothing you could detect. Pure arithmetic into a discarded variable is the easy case; anything that touches the outside world is not.

Reading the Clue

When a -O2 listing is dramatically shorter than your source suggests, and you have already accounted for folding and inlining, dead code elimination is usually the rest of the story. The practical reading skill is to ask, for each value your source computes, "does anything observable depend on this?" If the answer is no, expect it to be absent from the optimized listing. Lines that feed the return value, a side effect, or a later computation survive; lines that feed nothing do not.

Key Takeaways

  • Dead code elimination removes computations whose results are never used and branches that can never execute.
  • An unused result like x * 99 stored into a variable nobody reads is dropped entirely at -O2.
  • Unreachable code such as an if (false) block produces no instructions.
  • This is justified by the as-if rule: the compiler must preserve observable behavior, and unobserved work makes no observable difference.
  • Other optimizations (inlining, constant folding) constantly create dead code; elimination is the cleanup pass that removes it.
  • Work with observable side effects (printing, I/O, modifying globals) is never removed just because its return value is ignored.