A simple C expression becomes a sequence of lower-level operations that read values, use CPU working locations, perform an operation, and write a result.

Learning Question

What happens to a C expression such as result = left + right; when it gets compiled?

At the C level, the expression is written in terms of variable names and an operator.

At the machine level, the CPU does not evaluate C syntax. It executes instructions.

The first mental model is:

A C expression becomes instruction-level work over values stored in registers and memory.

This chapter uses one small expression to make that mapping visible.

The Source-Level View

Consider this C function:

int add(int left, int right)
{
    int result = left + right;
    return result;
}

The important line is:

int result = left + right;

At the C level, this line has several source-level concepts:

C Source ConceptMeaning at the C Level
lefta named function parameter
righta named function parameter
+the integer addition operator
resulta named local variable
= / initializationa value is stored into result

This is the right way to understand the source program when writing C.

But it is not the way the CPU executes the program.

The CPU does not know that left is a C parameter, that result is a C local variable, or that left + right is a C expression.

Those source-level concepts must be represented as lower-level operations.

The Instruction-Level Shape

Ignoring exact syntax for a moment, the expression has this lower-level shape:

get the value represented by left
get the value represented by right
add the two values
put the result somewhere associated with result

That is the core translation idea.

The source code says what relationship should hold:

result should contain the sum of left and right

The instruction-level form must perform work that makes that relationship true at runtime.

This is the first important difference:

C expresses the operation as meaning. Assembly exposes the operation as steps.

Values Must Be Somewhere

At the C level, it is natural to talk as if left, right, and result are directly available by name.

At the lower level, values must be in actual storage locations.

For this kind of expression, the relevant locations are usually:

  • registers
  • memory locations

A register is a small storage location inside the CPU.

Memory is the larger addressable storage visible to the running process.

The exact location of a value depends on the compiler, the target platform, the optimization level, and the surrounding code.

For now, the key point is:

Before the CPU can add values, those values must be available in locations that an instruction can use.

In many instruction sets, arithmetic operations are performed through registers. A value may be loaded from memory into a register, changed in a register, then stored back to memory if needed.

A Real Assembly Example

The following example is based on x86-64 Linux assembly generated by GCC with optimization disabled:

gcc -S -O0 add.c -o add.s

The exact output may vary by compiler version, platform, and compiler options.

The full C function is:

int add(int left, int right)
{
    int result = left + right;
    return result;
}

One possible generated assembly sequence for the function body includes:

movl    %edi, -20(%rbp)
movl    %esi, -24(%rbp)
movl    -20(%rbp), %edx
movl    -24(%rbp), %eax
addl    %edx, %eax
movl    %eax, -4(%rbp)
movl    -4(%rbp), %eax

This is not the only possible assembly for the C function.

It is one useful readable example because -O0 keeps the generated code close to the source-level structure.

Reading the Example at a High Level

Do not try to memorize every register name yet.

Read the sequence by role.

The first two instructions store incoming argument values into stack-related locations:

movl    %edi, -20(%rbp)
movl    %esi, -24(%rbp)

At this point, do not worry about why the arguments arrive in %edi and %esi, or why the stack locations are written as offsets from %rbp.

Those details belong to calling conventions and stack frames.

For this chapter, the important idea is simpler:

The values associated with left and right have been placed in concrete storage locations.

The next two instructions load those values into registers:

movl    -20(%rbp), %edx
movl    -24(%rbp), %eax

Now the CPU has working copies of the values in registers.

The addition instruction performs the arithmetic:

addl    %edx, %eax

In this AT&T-style syntax, this adds the value in %edx to the value in %eax, and stores the result in %eax.

The next instruction stores the computed value into the stack-related location used for result:

movl    %eax, -4(%rbp)

The last line in this short sequence loads result into the return-value register:

movl    -4(%rbp), %eax

The return-value convention will be explained later.

For this chapter, it is enough to notice that the value computed for result is moved again because returning a value also has a lower-level representation.

Mapping the C Expression to Operations

The C line:

int result = left + right;

corresponds to the central part of the assembly sequence:

movl    -20(%rbp), %edx
movl    -24(%rbp), %eax
addl    %edx, %eax
movl    %eax, -4(%rbp)

The conceptual map is:

Source-Level PartInstruction-Level Role
leftread the value from a concrete storage location
rightread the value from a concrete storage location
+perform an addition instruction over register-held values
resultwrite the computed value to a concrete storage location

The expression is not one CPU action.

It becomes a small sequence of actions.

Why the Compiler Uses Registers

The addition happens in registers:

movl    -20(%rbp), %edx
movl    -24(%rbp), %eax
addl    %edx, %eax

This shows a central machine-level pattern:

  1. Load values into CPU working locations.
  2. Perform the operation.
  3. Keep or store the result.

This does not mean every C variable becomes a register.

It also does not mean a register permanently belongs to one C variable.

Registers are temporary working locations used while instructions execute.

A C variable is a source-level name with C language rules.

A register is a CPU storage location.

The compiler decides how to use registers and memory to preserve the behavior required by the C program.

Why -O0 Makes the Mapping Easier to See

The example uses -O0, which disables most optimization.

That matters because optimized code may not preserve the source-level shape.

With optimization enabled, a compiler might:

  • keep values in registers without storing them to stack slots
  • remove the local variable result entirely
  • combine several operations
  • reorder operations when the observable behavior is preserved
  • replace a calculation with a constant if the inputs are known

For learning, -O0 is useful because it often shows a more direct relationship between source-level names and generated storage locations.

But -O0 output should not be mistaken for the deepest truth about C execution.

The durable lesson is not that this exact sequence must appear.

The durable lesson is:

The compiler must produce machine-level instructions whose behavior matches the C expression.

Source Meaning Versus Generated Form

The C expression describes required meaning:

result gets the sum of left and right

The generated instructions implement that meaning using the available machine mechanisms:

read values
move values into registers
add values
store or return the result

These are different levels.

The source-level question is:

What does this expression mean in C?

The instruction-level question is:

Which generated instructions read values, modify registers or memory, and preserve that meaning?

Assembly is useful because it lets those two questions be connected without treating them as the same question.

A Line of C May Not Equal One Instruction

The expression:

int result = left + right;

can correspond to several instructions.

That does not mean C is inefficient by definition.

It means C is a source language and the CPU executes instructions.

Also, the number of generated instructions is not stable across all builds.

It can change when the compiler, platform, optimization level, surrounding code, or target architecture changes.

So the right lesson is not:

This C line always becomes these exact instructions.

The better lesson is:

This C line must become some lower-level instruction behavior that obtains the operand values, computes the sum, and makes the result available where the rest of the program expects it.

What This Chapter Does Not Explain Yet

This chapter explains how a simple expression becomes lower-level operations.

It does not yet fully explain:

  • every register name
  • stack frame layout
  • why parameters arrive in %edi and %esi
  • why return values use %eax
  • the full syntax of AT&T assembly
  • optimized assembly output
  • how function calls work

Those topics matter, but they should come after the simple expression mapping is clear.

For now, the key boundary is:

C expressions define source-level meaning. CPU instructions implement that meaning by moving, reading, writing, and operating on values.

Core Mental Model

Keep these boundaries separate:

  • A C expression is written in terms of source-level names and operators.
  • The CPU executes instructions, not C expressions.
  • Values must be represented in concrete locations such as registers or memory.
  • Arithmetic usually happens through CPU instructions operating on those concrete locations.
  • One C expression may become several instructions.
  • The exact instruction sequence depends on the compiler, platform, optimization level, and surrounding code.

The useful question is not “which instruction is the C expression?”

The useful question is:

Which instructions together implement the meaning of this C expression?

Final Summary

A simple C expression such as result = left + right; does not run as source syntax.

It becomes lower-level instruction behavior: values are read from concrete locations, moved into CPU working locations, added, and written or returned in the places expected by the rest of the program.

Assembly makes that transformation visible.