C programs run when source-level concepts are translated into lower-level representations that the machine and operating system can execute.

Learning Question

What should I remember after connecting C, assembly, executable files, and running processes?

The goal of this collection is not to replace C thinking with assembly thinking.

The goal is to keep levels separate and connect them accurately.

C is the language used to express program behavior.

Assembly makes the instruction-level form visible.

Machine code is the binary instruction form the CPU executes.

An executable file packages machine code and metadata.

A process is the running instance with memory, registers, and operating-system state.

The final mental model is:

C source code describes behavior. The running program is machine instructions changing registers, memory, and control flow inside a process.

The Main Levels

The most important levels are:

LevelWhat It Answers
C source codeWhat behavior did the programmer describe?
AssemblyWhat instruction-level shape did the compiler produce?
Machine codeWhat binary instructions can the CPU execute?
Object fileWhat compiled pieces exist before final linking?
Executable fileWhat loadable program file was produced?
ProcessWhat running instance exists in memory right now?

Most confusion comes from asking a question at one level and answering it with another level’s vocabulary.

For example:

Where is the variable?

At the C level, the variable is a named source-level entity.

At the instruction level, the value associated with that name may be in memory, in a register, moved between them, or optimized away as a separate location.

The better question is:

At this point in the generated instructions, where is the value represented?

Core Mapping Table

The following table is the compact map for the whole collection.

C ConceptLower-Level CounterpartImportant Boundary
variablevalue in memory, value in a register, or optimized-away representationA variable name is not automatically a fixed runtime address.
expressionsequence of instructionsOne C expression may become several instructions, or be optimized into a different shape.
assignmentwrite to a storage locationThe target may be memory or a register-held representation.
function callargument placement, call, stack-frame state, return value, retA call is a controlled handoff, not just a source-level phrase.
pointeraddress valueThe pointer value is separate from the object at that address.
array accessbase address plus scaled indexThe index is scaled by element size and does not automatically carry bounds.
struct field accessbase address plus field offsetField offsets come from layout and alignment rules.
return valuevalue placed where the calling convention expectsOn many x86-64 examples, an int return value appears in %eax.
executable fileloadable file containing machine code, data, and metadataThe executable is not the running process.
processrunning instance with address space, stack, heap, registers, and OS stateMultiple processes can come from the same executable file.

This table should not be memorized as a slogan.

It should be used as a diagnostic tool when a low-level question feels confusing.

Variables

At the C level, a variable has a name, type, scope, and lifetime.

At the lower level, the value associated with that variable must be represented somehow while the program runs.

It may be:

  • stored in a stack-frame location
  • stored in global memory
  • held in a register
  • moved between memory and registers
  • removed as a separate runtime location by optimization

So the statement:

local variables are on the stack

is only a first approximation.

The more precise statement is:

A local variable may be represented by a stack location, but the compiler is free to use any representation that preserves the required C behavior.

Expressions and Assignments

C expressions describe relationships between values:

result = left + right;

At the lower level, that expression becomes work such as:

read left
read right
add values
write result

Assembly may show:

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

The exact sequence can vary.

The durable idea is:

C expressions state meaning. Generated instructions implement that meaning through data movement, arithmetic, memory access, and register use.

Registers

Registers are the CPU’s working space.

They are not C variables.

They temporarily hold values while instructions execute.

A register can hold one source-level value at one moment and a different source-level value later.

When reading assembly, the useful question is:

What value does this register hold at this instruction, and what instruction last wrote it?

That question prevents the common mistake of trying to permanently map one C variable to one register.

Stack Frames

A stack frame is the stack region associated with one active function call.

Unoptimized assembly often uses stack-frame offsets such as:

-20(%rbp)

to store values associated with parameters, local variables, or temporary compiler storage.

This does not mean the C variable literally is the offset.

It means the generated code is using that stack location to represent a value needed by that function call.

The useful question is:

Which active function call owns this stack-frame location, and what value is it being used to hold?

Function Calls

A C function call hides a machine-level protocol.

The caller and callee must agree on:

  • where arguments are placed
  • how control transfers
  • what state is preserved
  • where the return value appears
  • how execution resumes after the call

For example:

movl    $3, %esi
movl    $2, %edi
call    add
movl    %eax, -4(%rbp)

This sequence prepares arguments, transfers control to add, then stores the returned value.

The C expression:

int value = add(2, 3);

is compact because the calling convention and generated code carry the lower-level details.

Pointers

A pointer is an address value with a C type.

The pointer value and the pointed-to object are different things.

In:

int value = 10;
int *p = &value;
*p = 20;

p stores an address.

*p accesses memory at that address.

The source-level type int * tells C how to interpret dereference and pointer arithmetic.

The address value tells generated instructions where memory is.

The useful question is:

Am I working with the address value, or with the object at that address?

Arrays and Structs

Arrays and structs are both about memory layout.

An array access uses:

base address + index * element_size

A struct field access uses:

base address + field_offset

An array of structs combines both:

base address + index * sizeof(struct_type) + field_offset

C syntax hides those calculations:

values[i]
point.y
points[i].y

Assembly can reveal the address arithmetic underneath.

The useful question is:

What base address and offset identify the selected object?

Executable Files

The executable file is the loadable representation of the program.

It is produced by preprocessing, compilation, assembly, and linking.

It may contain:

  • machine code
  • read-only data
  • initialized data
  • metadata for loading
  • symbol and dynamic-linking information

But it is still a file.

It is not yet a running program.

The useful question is:

Am I looking at a build artifact on disk, or at runtime state inside a process?

Processes

A process is a running instance of a program.

It has:

  • mapped code and data
  • stack
  • heap
  • registers
  • an instruction pointer
  • operating-system-managed state

The same executable can start multiple processes.

Each process has its own runtime state.

The useful question is:

Which process instance and which point in its execution am I reasoning about?

The Full Path

The whole path can be summarized as:

C source code
-> preprocessing
-> compilation
-> assembly / machine code
-> object file
-> linking
-> executable file
-> operating system prepares process
-> CPU executes instructions
-> registers and memory change

This flow is simplified, but it preserves the important boundaries.

The C source file is not directly executed.

The assembly text is not directly executed as text.

The executable file is not the running process.

The process is not just the source program.

The running program is instruction execution inside a process.

How to Read Low-Level Evidence

When using assembly, debugger output, disassembly, or process tools, first identify the level:

EvidenceLevel
.c filesource code
.s fileassembly text
.o fileobject file
executable fileloadable program file
debugger stack frameruntime process state reconstructed for inspection
register dumpcurrent CPU state for a stopped process
memory mapprocess address-space layout

Then ask a level-appropriate question.

For example, do not ask:

Where is the C line?

Ask:

Which generated instructions correspond to this source construct?

Do not ask:

Which address is the variable?

Ask:

At this instruction, where is the value associated with that source-level variable represented?

What This Collection Does Not Finish

This collection builds the first programmer-facing mental model of how C runs.

It does not fully teach:

  • all x86-64 assembly
  • full compiler design
  • full ABI details
  • operating-system internals
  • virtual memory implementation
  • dynamic linking internals
  • CPU microarchitecture
  • performance optimization

Those subjects are deeper layers.

The purpose here is to make them approachable later by separating the main levels first.

Core Mental Model

Keep these boundaries separate:

  • C source code is the programmer-facing description of behavior.
  • Assembly is a readable instruction-level view.
  • Machine code is the binary instruction form the CPU executes.
  • Object files are compiled pieces before final linking.
  • Executable files are loadable program files.
  • Processes are running instances with memory and CPU state.
  • Registers and memory are where runtime values are represented.
  • Stack frames organize active function calls.
  • Pointers, arrays, and structs become address values and address calculations.

The central question for low-level understanding is:

How is this source-level concept represented at this point in the generated program and running process?

Final Summary

C programs do not run as C source text.

They are translated into lower-level forms, linked into executable files, loaded into processes, and executed as machine instructions that change registers, memory, and control flow.

Assembly is the bridge that lets a C programmer inspect how source-level concepts become instruction-level behavior without reading raw machine code.

The durable understanding is not a list of instruction names.

It is the ability to keep the levels separate while connecting them accurately.