A C file is readable text, but the computer eventually runs machine instructions. That gap can feel like one black box, especially when errors mention a preprocessor, compiler, assembler, or linker. Build one file first, then trace the value 1200 through a two-file project from preprocessing to execution. GCC exposes each boundary through a distinct command in a Unix-style shell. Other toolchains may use different commands and suffixes, but the responsibilities remain similar.
Start with the smallest complete C program
Create hello.c:
#include <stdio.h>
int main(void) {
int units = 3;
printf("Units: %d\n", units);
return 0;
}Build and run it:
gcc -Wall -Wextra -std=c11 hello.c -o hello
./helloThe output is exactly Units: 3. The #include directive is handled before C syntax is compiled. main is the source-level entry function, units stores 3, printf formats the line, and \n ends it. gcc drives several tools, so saying it compiled hello hides the stages below.
Running it proves that your local build path works. The Coding & Skill Development Courses page provides the broader learning route.
Set up the project every stage will trace
Use exactly three files:
/* bill.h */
#ifndef BILL_H
#define BILL_H
#define TAX_RATE 5
int tax_amount(int price);
#endif/* tax.c */
#include "bill.h"
int tax_amount(int price) {
return price * TAX_RATE / 100;
}/* main.c */
#include <stdio.h>
#include "bill.h"
int main(void) {
int price = 1200;
int tax = tax_amount(price);
printf("Base: %d\n", price);
printf("Tax: %d\n", tax);
printf("Total: %d\n", price + tax);
return 0;
}Build with gcc -Wall -Wextra -std=c11 main.c tax.c -o bill, then run ./bill. The calculation is 1200 * 5 / 100 = 60, followed by 1200 + 60 = 1260:
Base: 1200
Tax: 60
Total: 1260main.c calls a function defined in tax.c. bill.h gives both sources the same declaration and macro. A header is normally included into a source, not compiled as an independent program. The C Tutorial: 11-Week Path to Pointers and Projects spans the broader learning sequence; this three-file build isolates the intermediate .i, .s, and .o artifacts produced by its compilation stage.
Preprocessing creates the text the compiler sees
Run:
gcc -E main.c -o main.i
gcc -E tax.c -o tax.iPreprocessing handles # directives, exposes included declarations, and replaces macros. Search the large main.i for main and tax_amount instead of printing expanded <stdio.h>. Find int tax_amount(int price); before the call. In tax.i, find return price * 5 / 100;.
If one source encounters bill.h twice, the first inclusion defines BILL_H and exposes the macro and declaration. The second sees it already defined and skips the guarded block. This is a preprocessing feature, not a linker feature.
Macro replacement is textual. It changes TAX_RATE to 5; it does not calculate the eventual tax of 60.

Compilation and assembly create object files
Generate assembly from the preprocessed files:
gcc -S main.i -o main.s
gcc -S tax.i -o tax.sThis stage parses tokens, checks syntax and types, and generates target-specific assembly. In main.i, tax_amount accepts and returns int; the call supplies the int named price. Token recognition belongs to a compiler front end; Lexical Analysis in Compiler Design: Tokens to DFA Scanner explains that front-end step. The toolchain then proceeds through preprocessing, compilation, assembly, linking, loading, and execution.
Now assemble both files:
gcc -c main.s -o main.o
gcc -c tax.s -o tax.oThe assembler encodes instructions and metadata into relocatable objects. main.o contains main but still refers to tax_amount and printf; tax.o defines tax_amount. Neither is runnable. The shorthand gcc -c main.c -o main.o performs the first three stages, then stops before linking.
Linking resolves names, then loading starts the program
Link with gcc main.o tax.o -o bill. The tax_amount reference in main.o matches its definition in tax.o. GCC also arranges for printf to resolve from the C runtime libraries. Relocation assigns locations so calls and data references point correctly.
The linker produces bill. Running ./bill makes the operating system load it and required runtime support into a process. Startup code transfers control to main, which calculates 60 and prints the result.
The expanded reconstruction is:
gcc -E main.c -o main.i
gcc -E tax.c -o tax.i
gcc -S main.i -o main.s
gcc -S tax.i -o tax.s
gcc -c main.s -o main.o
gcc -c tax.s -o tax.o
gcc main.o tax.o -o billThis route and the one-line command have the same result. The long route reveals boundaries; ordinary builds need not expose them.

Diagnose failures by their stage
Cause | Stage and symptom | Fix |
|---|---|---|
| Preprocessing cannot find the requested file | Restore it or correct the include path |
| Compilation sees | Remove the semicolon and inspect |
Header declares | Compiling | Make declaration and definition identical |
| Linking cannot resolve | Include |
Diagnostic wording varies, so identify the stage. A logic bug may still build: price * (TAX_RATE / 100) makes integer division 5 / 100 equal 0, then prints Tax: 0 and Total: 1200. Restore price * TAX_RATE / 100. A runnable executable does not prove its logic.
Test yourself on the compilation model
Try each check before reading its answer.
After
gcc -c main.c -o main.o, what ended? Answer: assembly is the last completed stage, and no linkedbillhas been created.When does
TAX_RATEbecome5? Answer: during preprocessing, not linking.What happens if
tax.ois omitted? Answer: linking fails becausetax_amountremains unresolved.Which file defines
tax_amount? Answer:tax.c;bill.honly declares it.
Value exercise: Change TAX_RATE to 12 and price to 850. Predict first. Answer: 850 * 12 / 100 = 102, so the output is Base: 850, Tax: 102, Total: 952. This integer calculation happens to divide without a remainder.
Build exercise: After editing only tax.c, what is the shortest separate build? Answer: run gcc -Wall -Wextra -std=c11 -c tax.c -o tax.o, keep main.o, then run gcc main.o tax.o -o bill.
Diagnosis exercise: Change the call to tax_amount(price, 5) while the declaration accepts one argument. Answer: compilation reports an argument-count mismatch before linking.
C compilation model: short version and next step
Remember this chain:
Source files and headers provide text.
Preprocessing handles directives, includes, and macros.
Compilation checks C and generates assembly.
Assembly creates relocatable objects.
Linking resolves cross-file and library symbols into an executable.
Loading creates a running process.
Execution produces the program's behaviour.
main.o can exist while bill does not. bill can run while still containing a logic error.
Build and confirm 1200 to 60 to 1260. Find literal 5 in tax.i. Create both objects without linking and see that no new bill appears, then link and run. Finally try 850 and 12. Change one element at a time so the responsible stage remains visible.
For a structured sequence, continue with the C Language course. If you want to carry these fundamentals into broader coding practice, use Coding for Placements.




