Multi-File Programs and Headers
Splitting a program into modules with .h and .c files.
7 minute read
Big idea
Real programs are split over many .c files. A header file (.h) lists what one file offers — its declarations — so other files can use it. Each .c file is compiled on its own, then the linker joins them.
Why split at all?
- Find things.
list.cholds the list code;main.cholds the program. No 5,000-line file. - Reuse. Copy
list.candlist.hinto your next project. - Faster builds. Change one file and only that file is recompiled.
The three files
Say we want a tiny "point" module. The header is the menu — what's available:
// point.h
#ifndef POINT_H
#define POINT_H
typedef struct {
int x, y;
} Point;
Point point_add(Point a, Point b);
void point_print(Point p);
#endif
The source file is the kitchen — how it's done:
// point.c
#include <stdio.h>
#include "point.h"
Point point_add(Point a, Point b) {
Point r = {a.x + b.x, a.y + b.y};
return r;
}
void point_print(Point p) {
printf("(%d, %d)\n", p.x, p.y);
}
And main.c just orders from the menu:
// main.c
#include "point.h"
int main(void) {
Point a = {1, 2}, b = {10, 20};
point_print(point_add(a, b));
return 0;
}
Build all of it with one command:
gcc main.c point.c -o app
./app
(11, 22)
What really happens
When main.c compiles, the compiler has only seen the header — the declaration Point point_add(Point a, Point b);. That's enough to check the call is right. The actual code (the definition) lives in point.o; the linker connects the call to it at the end.
Watch it
Declarations go in .h, definitions in .c
In the header (.h) | In the source (.c) |
|---|---|
| function prototypes | function bodies |
typedefs, struct and enum definitions | the variables themselves |
#define constants | static helpers nobody else needs |
extern int counter; | int counter = 0; |
Put a function body in a header and include it from two files, and the linker complains it's defined twice.
static and extern across files
From Scope and Storage:
- A global or function marked
staticis private to its.cfile. Use it for helpers — two files can each have astatic int helper(void)without clashing. externsays "this variable exists; it's defined in some other file". It goes in the header, and exactly one.cfile has the real definition.
Try the idea in one file
The runner here compiles one file, so this program marks where each file's part would go. The structure is exactly the same:
#include <stdio.h>
/* ===== counter.h — the interface ===== */
void counter_add(int amount);
int counter_value(void);
extern int counter_calls; // declared here…
/* ===== counter.c — the implementation ===== */
static int total = 0; // private: only this file can touch it
int counter_calls = 0; // …defined exactly once, here
static int clamp(int v) { // private helper
return v < 0 ? 0 : v;
}
void counter_add(int amount) {
total = clamp(total + amount);
counter_calls++;
}
int counter_value(void) {
return total;
}
/* ===== main.c — uses only what counter.h offers ===== */
int main(void) {
counter_add(5);
counter_add(-8);
counter_add(3);
printf("value=%d calls=%d\n", counter_value(), counter_calls);
return 0;
}output
value=3 calls=3Run and edit it in the app.
main can't read total directly — in a real split it wouldn't even compile — so the only way to change it is through counter_add. That's encapsulation, in plain C.
Which of these belongs in a header file?
Common mistakes
- "undefined reference to
point_add" — a linker error. The compiler was happy (it saw the prototype), but you didn't compilepoint.cin:gcc main.c point.c. - "multiple definition of
count" —int count = 0;is in a header included by two files. Move it to one.c, putextern int count;in the header. - No include guard — the same
typedefseen twice is an error. Always guard headers.
Think of it like
A restaurant menu and its kitchen. Diners (other files) only see the menu (header): dish names and what goes in. The kitchen (.c file) knows the recipes and keeps its own tools (static) to itself.
Key terms
- Header file (
.h) — Declarations shared between files. - Source file (
.c) — Definitions: function bodies and variables. - Declaration — Says something exists and what type it is.
- Definition — Actually creates it (the body, or the storage).
- Linker — Joins compiled files into one program, matching calls to definitions.
- Translation unit — One
.cfile after its#includes are pasted in.
Check yourself
What goes in a header file?
"undefined reference to point_add" means…
A static function in list.c can be called from…