Skip to content
FollowtheDamnCode

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.c holds the list code; main.c holds the program. No 5,000-line file.
  • Reuse. Copy list.c and list.h into 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

Each .c compiles alone; the linker joins them

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 prototypesfunction bodies
typedefs, struct and enum definitionsthe variables themselves
#define constantsstatic 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 static is private to its .c file. Use it for helpers — two files can each have a static int helper(void) without clashing.
  • extern says "this variable exists; it's defined in some other file". It goes in the header, and exactly one .c file 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:

A counter module, laid out file by file
#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=3

Run 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 compile point.c in: 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, put extern int count; in the header.
  • No include guard — the same typedef seen 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 .c file 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…