Why splitting files matters
When you compile a C program, the compiler processes one translation unit at a time.
Each .c file is a translation unit. They are compiled independently into
object files (.o), then linked together by the linker into a final executable.
This means you can change one .c file and recompile only that file —
not the entire program. On a project with 200 source files, this is the difference
between a 2-second rebuild and a 3-minute rebuild.
Headers — the interface contract
A header file (.h) declares what a module exposes.
It lists function signatures and type definitions — but no function bodies.
The .c file contains the actual implementations.
Any .c file that wants to call a function from utils.c
includes utils.h. The compiler sees the declarations and knows
the types. The linker resolves the actual addresses at link time.
#ifndef / #define / #endif, including the same header twice
(which happens easily through transitive includes) causes duplicate definition errors.
Alternatively, #pragma once works in all modern compilers and is less boilerplate.
static and extern — controlling visibility
By default, a function or global variable defined in a .c file is
visible to all other translation units — the linker can see it.
This is called external linkage.
static at file scope restricts a symbol to its own translation unit.
Other files cannot see it. It's private to that module.
extern tells the compiler: "this symbol exists, but it's defined in
another file. Don't allocate storage here — just trust it exists."
static on a local variable means something different:
the variable persists across function calls (stored in the data segment, not the stack).
Same keyword, different context, different meaning.
Makefiles
Typing the compile commands by hand gets old fast.
make reads a Makefile and figures out which files need rebuilding
based on timestamps. If utils.c changed but main.c didn't,
only utils.o is recompiled.
A Makefile is a list of rules. Each rule says: "to build target, run these commands, and it depends on these prerequisites."
make with a cryptic error.
If your Makefile isn't working, check for space-vs-tab confusion first.
A minimal project layout
A real project separates source files, headers, and build artifacts.
This keeps things navigable and prevents the root directory from filling up with
.o files.
Split into .c + .h pairs, use static to hide internals, and let Make handle what to recompile.