CMulti-File Projects

Multi-File Projects

Once a program is split across several `.c` files (see Modular Programming), you need to know how to actually compile and link them into a single executable. GCC supports this directly, and understanding the two-step process — **compile**, then **link** — explains why large C projects build so much faster after small changes.
The One-Step (Naive) Way

The simplest approach is to hand every source file to gcc at once and let it do everything in a single command.

Bash
gcc main.c stack.c queue.c -o program

This works, but it has a hidden cost: every single file gets recompiled every time, even if you only changed one line in one file. For a project with dozens or hundreds of source files, that becomes painfully slow.

The Two-Step Process: Compile, Then Link

GCC (like essentially all C toolchains) actually performs two distinct steps under the hood:

  • Compilation — each .c file is translated independently into an object file (.o), using the -c flag. Object files contain machine code, but with unresolved references to functions/variables defined elsewhere.

  • Linking — the object files (and any needed libraries) are combined into a single executable. The linker resolves all the cross-file references, matching each call to its definition.

Bash
# Step 1: compile each source file into an object file, independently
gcc -c main.c -o main.o
gcc -c stack.c -o stack.o
gcc -c queue.c -o queue.o

# Step 2: link the object files together into the final executable
gcc main.o stack.o queue.o -o program
Note
The `-c` flag tells gcc "compile only, don't link" — it stops after producing the `.o` file. Without `-c`, gcc would try to link immediately, and would fail if any file lacks a `main` function or references symbols defined elsewhere.
Why This Is Faster for Large Projects

The key insight: if you only edit stack.c, you only need to recompile stack.c into a fresh stack.o. The already-compiled main.o and queue.o are untouched and can be reused as-is — only the final, comparatively cheap linking step needs to rerun.

Bash
# You edited only stack.c. Recompile just that file...
gcc -c stack.c -o stack.o

# ...then relink using the existing object files.
gcc main.o stack.o queue.o -o program

On a project with 3 files this saves little time. On a project with 300 files, where a full rebuild might take minutes, incremental compilation like this can turn a multi-minute rebuild into a near-instant one.

Compilation vs Linking at a Glance

Step

Input

Output

Flag

Compile

One .c file

One .o object file

-c

Link

Multiple .o files (+ libraries)

One executable

(none — default gcc behavior)

Common Linker Errors

If the linker can't find the definition of something a .o file references, you'll see an "undefined reference" error — usually meaning you forgot to include that object file (or library) in the final link command, or you have a typo in a function name between the header declaration and its implementation.

Manually Tracking Object Files Doesn't Scale Either
Typing out `gcc -c` commands for every file, then remembering which ones changed and need recompiling, is exactly the kind of repetitive bookkeeping that computers should do for us. That's precisely the problem the classic Unix build tool **Make** solves — see Build Basics with Make for how it automates this dependency tracking.
Tip
Object files (`.o`) are platform- and compiler-specific intermediate artifacts. They're not meant to be checked into version control — add `*.o` to your `.gitignore`.
Warning
Every `.c` file that defines a `main()` function will conflict with any other `.c` file that also defines `main()` when you try to link them together — a program can have exactly one entry point.