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:

TXT
weather/
├── stats.h      ← the interface: what other files may use
├── stats.c      ← the implementation
└── main.c       ← the program that uses it
H stats.h
#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
C stats.c
#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;
}
C main.c
#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;
}
Output
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 prototypesFunction definitions (bodies)
Type definitions: structs, enums, typedefsPrivate helpers, marked static
extern declarations of shared variablesThe single definition of each shared variable
Macros and constants others needAnything 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:

C
// 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.

Try it yourself · main.c
Edit the code, copy it into main.c, then compile and run it with cc -std=c17 -Wall main.c -o main && ./main. How to set up a C compiler
Output
range: 7.50