Splitting a Program into Files
Real C programs are split into many source files, each compiled separately and then linked together. Header files (.h) declare what each part offers; source files (.c) contain the definitions. This lesson builds a small multi-file program and explains extern, static and separate compilation.
The structure
A small temperature-statistics program in three files:
weather/ ├── stats.h ← the interface: what other files may use ├── stats.c ← the implementation └── main.c ← the program that uses it
#ifndef STATS_H #define STATS_H #include <stddef.h> typedef struct { double min; double max; double mean; } Summary; // Summarise n values. Returns 0 on success, -1 if n is 0. int summarise(const double *values, size_t n, Summary *out); // Number of calls to summarise() so far. extern int summarise_calls; #endif
#include "stats.h" int summarise_calls = 0; // the one definition of the variable static double add(double a, double b) { // private helper: not visible outside this file return a + b; } int summarise(const double *values, size_t n, Summary *out) { summarise_calls++; if (n == 0) return -1; Summary s = { values[0], values[0], 0 }; double total = 0; for (size_t i = 0; i < n; i++) { if (values[i] < s.min) s.min = values[i]; if (values[i] > s.max) s.max = values[i]; total = add(total, values[i]); } s.mean = total / n; *out = s; return 0; }
#include <stdio.h> #include "stats.h" int main(void) { double week[] = { 14.5, 17.0, 12.25, 19.75, 16.0, 13.5, 18.25 }; Summary s; if (summarise(week, sizeof week / sizeof week[0], &s) == 0) { printf("min %.2f, max %.2f, mean %.2f\n", s.min, s.max, s.mean); } Summary none; if (summarise(week, 0, &none) != 0) { printf("no data to summarise\n"); } printf("summarise was called %d times\n", summarise_calls); return 0; }
min 12.25, max 19.75, mean 15.89 no data to summarise summarise was called 2 times
Building it
gcc -std=c17 -Wall -Wextra main.c stats.c -o weather ./weather
Or compile each file to an object file separately, then link them — so after a change, only the changed file needs recompiling:
gcc -std=c17 -Wall -Wextra -c stats.c # produces stats.o gcc -std=c17 -Wall -Wextra -c main.c # produces main.o gcc main.o stats.o -o weather # link
Lesson 53 automates this with make.
What goes where
In the header (.h) | In the source file (.c) |
|---|---|
| Function prototypes | Function definitions (bodies) |
| Type definitions: structs, enums, typedefs | Private helpers, marked static |
extern declarations of shared variables | The single definition of each shared variable |
| Macros and constants others need | Anything only this file needs |
| An include guard | #include of its own header first |
Including its own header first in stats.c lets the compiler check that the definitions match the declarations everyone else sees.
extern and the one-definition rule
A function or global variable may be declared many times but defined exactly once in the whole program. extern int summarise_calls; in the header is a declaration ("this exists somewhere"); int summarise_calls = 0; in stats.c is the definition. Putting a definition in a header that several files include gives the linker duplicate symbols.
static means private
static on a function or global variable gives it internal linkage: it can't be seen from other files. Make everything static unless another file needs it — the same idea as "private" in other languages. Trying to use the private helper from main.c fails at link time:
// main.c #include <stdio.h> #include "stats.h" double add(double a, double b); // claims add() exists somewhere... int main(void) { printf("%.1f\n", add(1.0, 2.0)); return 0; }
Surprisingly, main.c compiles without complaint: each file is compiled on its own, and the compiler trusts the declaration. The error comes from the linker, which can't find a non-static add in any object file. GCC on Linux reports "undefined reference to `add'"; Clang on macOS reports "Undefined symbols … \"_add\", referenced from: _main".
Libraries
Several object files can be bundled into a static library (ar rcs libstats.a stats.o) and linked with -L. -lstats. Shared libraries (.so on Linux, .dylib on macOS, .dll on Windows) are loaded at run time instead. Both are how C code is packaged for reuse.
Try it yourself
Add a function range (max − min) to stats.h and stats.c, call it from main.c, and rebuild with both files.
range: 7.50