A program on disk does nothing. It is just bytes — first as the text you typed, later as machine instructions. Turning those bytes into a result that shows up on your screen is a journey with clearly marked checkpoints. In this lesson we walk a tiny C program, hello.c, from a text file all the way to a finished process, and we name every stage it passes through. Once you can see the stages, you can answer the everyday question "why did it break here?" with precision.
The lifecycle: from text to result
Clicking Run hides a pipeline. Your editor hands a source file to a compiler, which translates human-readable code into machine instructions. A linker then stitches your code together with library code into a single executable file. When you launch it, the operating system's loader copies that file into memory, creates a process, and finally hands control to the CPU, which executes the instructions one by one until a result comes back.
The crucial mental shift: these are different kinds of thing, not the same file changing color. The source is text. The executable is machine code on disk. The process is that code in motion inside memory, with its own registers, stack, and a slice of the CPU's attention.
Compiling and linking: text becomes a program
Compilation happens before your program ever runs. The compiler reads your source, checks that it is well-formed, and emits machine instructions for your CPU. But a typical compile produces an object file with holes in it: every call to a library function like printf is left as an unresolved reference. The linker fills those holes, splicing in the library code (or a reference to a shared library) and producing one runnable executable.
This split is why two very different errors exist. A compile error ("expected ';'") means your text was malformed. A link error ("undefined reference to foo") means the text was fine, but the linker could not find the actual code for something you called.
$ cat hello.c
#include <stdio.h>
int main(void) {
printf("hello\n");
return 0;
}
$ gcc hello.c -o hello # compile + link in one command → an executable named "hello"
$ ./hello # ask the OS to load and run it
hello
That single gcc command quietly runs several tools in sequence: the preprocessor expands #include, the compiler (cc1) turns C into assembly, the assembler (as) turns assembly into an object file, and the linker (ld) produces the final hello executable. You will pry these apart in the exercise.
Loading and execution: where the OS steps in
Typing ./hello does not run the program directly — it asks the operating system to. The OS is the gatekeeper for the CPU and memory, so it does the setup: it opens the executable, maps its code and data into a fresh region of memory, lays out a stack, creates a process (a bookkeeping record plus that memory), and finally sets the CPU's program counter to the program's entry point. Only then does the CPU begin fetching and executing your instructions.
This is also why the same executable can run many times at once — each launch gets its own fresh process and its own private memory. The file on disk never changes; the OS just stamps out a new running copy every time you click Run.
Letting any program load itself directly into memory would let it stomp on other programs (or the OS). By forcing every launch through the OS, the system can enforce isolation, set permissions, and account for resources. The loader is a security checkpoint as much as a convenience.
Run gcc -v hello.c -o hello. The -v (verbose) flag makes gcc print every sub-tool it invokes. Find the lines that mention cc1, as, and a linker (ld or collect2), and match each one to a stage from this lesson's lifecycle diagram.
Show the answer
cc1 is the actual C compiler — it does the compile stage (source → assembly). as is the assembler, turning that assembly into an object file (still part of the compile/assemble step). collect2/ld is the linker, combining your object file with the C library's startup code and printf to produce the final hello executable. The preprocessor (#include expansion) ran first, folded into the cc1 invocation. Notice that load and execute are absent here — those happen later, when you actually type ./hello and the OS takes over.
What is the difference between a compile error and a link error?
At which stage does the operating system first take control?
- A program's life runs source → compile → link → load → execute → result.
- Source is text, an executable is machine code on disk, a process is that code running in memory — three different things.
- The compiler translates code; the linker resolves references and produces one runnable file.
- The OS loader steps in at launch: it builds the process, maps memory, and hands the CPU its starting point.