C Mastery / Multi-File Programs and Headers
Part 1 — The Core Language

Multi-File Programs and Headers

This chapter brings together declarations, definitions, linkage, and include guards to show how real C programs are organized across multiple files and headers.

Why This Matters

Almost no real C program is a single file. The header/source split — with declarations in .h files and definitions in .c files — is the fundamental organizational pattern of C. Getting it right is what makes large codebases buildable and maintainable.

Prerequisites

Core Concept

The header/source split

variable declarations, type definitions, macros, and static inline functions) — things that must be shared.

definitions) — the actual code.

Each .c file is a separate translation unit, compiled independently, then linked together.

Public vs. private

with internal linkage.

Syntax

A typical structure:

src/
  main.c
  point.c
include/ (or same dir)
  point.h

point.h:

#ifndef POINT_H
#define POINT_H

typedef struct Point {
    int x;
    int y;
} Point;

Point point_add(Point a, Point b);
double point_distance(Point a, Point b);

#endif

point.c:

#include "point.h"
#include <math.h>

Point point_add(Point a, Point b)
{
    Point r = { a.x + b.x, a.y + b.y };
    return r;
}

double point_distance(Point a, Point b)
{
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    return sqrt(dx*dx + dy*dy);
}

main.c:

#include "point.h"
#include <stdio.h>

int main(void)
{
    Point a = {1, 2}, b = {4, 6};
    Point c = point_add(a, b);
    printf("%d %d\n", c.x, c.y);
    return 0;
}

How It Works

Each .c file is preprocessed (headers expanded), compiled to an object file, and then all object files are linked. The header ensures both point.c and main.c agree on the type and signatures of Point and point_add.

Variations

Opaque types

To hide a struct's definition from users, declare only the type in the header and define it in the source:

/* point.h */
typedef struct Point Point;
Point *point_create(int x, int y);
int point_get_x(const Point *p);
void point_destroy(Point *p);
/* point.c */
struct Point { int x; int y; };

Users can hold a Point * but cannot see or touch the members. This is the core of encapsulation in C.

Header-only libraries

Some libraries put everything in headers as static inline functions. This is convenient but can bloat compile times and binary size.

Common Mistakes

Undefined Behavior

different signature (incompatible types) is a constraint violation and, if undiagnosed, UB.

required is a constraint violation.

Portability

structuring avoid them.

Under the Hood

The compiler processes each TU independently; the linker then resolves external symbols. Headers contain no code (except static inline and macros), so they produce no duplicate definitions when included by many TUs.

Practical Usage

Exercises

1. Split a small program into a module (.h + .c) and a main.c; build and run with multiple TUs. 2. Add a static helper to the module and confirm it is not visible outside. 3. Convert a struct API to an opaque type and update the users. 4. Deliberately define a global in a header and observe the linker error, then fix it with extern.

Deep Challenge

Design a small "bank account" module with an opaque Account type and a public API (account_create, account_deposit, account_withdraw, account_balance, account_destroy), a private static helper, and a header with an include guard. Explain how opaque types and internal linkage provide encapsulation.

References

definitions).

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.multi.header06