The Wall One File Hits

Everything you have written so far has lived in a single file, and that stops working sooner than you would think. A file holding every struct, every helper and main is a file where nothing can be found and where changing one thing puts all the others at risk, and it is also a file only one person can comfortably work on at a time. Real C projects are built from many files, each holding one concern, and chapter 3 already pointed here: prototypes, it said, earn their keep when a program is split across several files. This is that lesson.

One thing to say plainly before anything else. The editor on this platform compiles a single file, so nothing in this lesson can be run in the box beside it. This is a mental model lesson rather than a hands-on one, and the model is not optional: it is what every C project you open is made of, and it is what makes sense of the two error messages you are most likely to meet, one of which chapter 1 showed you and left half explained. Every program, command and diagnostic below comes from a real multi-file build run with gcc -std=c17 -Wall -Wextra -O2, quoted as gcc printed it.

A Module in Three Files

A module is one concern given its own pair of files: a header with the extension .h, and a source file with the extension .c. What goes in which is decided by one sentence worth learning now, because the rest of this lesson is that sentence being enforced by the compiler and the linker. Headers declare; source files define. The header is the module's interface, the part its callers read and include. The source file is the implementation, and nothing outside the module ever needs to look at it.

Here is a module that summarizes an array of readings. Its header, stats.h, is exactly what a caller needs and not one line more:

#ifndef STATS_STATS_H
#define STATS_STATS_H

#include <stddef.h>

struct Summary
{
    double total;
    double mean;
};

extern int summarize_calls;

struct Summary summarize(const double *values, size_t count);

#endif

Three kinds of thing are in there and not one of them reserves a byte or produces a single instruction. The struct definition has to be here, because a caller writing struct Summary summary = summarize(...); needs the layout to declare the variable and the member names to read it. The prototype is chapter 3's declaration, a signature ending in a semicolon where the body would go. And extern int summarize_calls; declares a variable rather than defining one, a distinction with a section of its own below. Notice #include <stddef.h> too, which is a rule rather than a courtesy: a header must compile on its own. This one uses size_t, so it includes the header that defines size_t instead of hoping that whoever includes it got there first. Delete that line, ask gcc to check the header by itself, and it is error: unknown type name 'size_t', with a note suggesting the very include you removed.

The source file stats.c holds the bodies, and it holds one function no caller will ever hear about:

#include "stats.h"

int summarize_calls = 0;

static double total_of(const double *values, size_t count)
{
    double total = 0.0;

    for (size_t i = 0; i < count; ++i)
    {
        total += values[i];
    }

    return total;
}

struct Summary summarize(const double *values, size_t count)
{
    struct Summary result = {.total = total_of(values, count), .mean = 0.0};

    ++summarize_calls;

    if (count > 0)
    {
        result.mean = result.total / (double)count;
    }

    return result;
}

Two details in the first line. #include "stats.h" uses quotes rather than angle brackets, and the difference is where the preprocessor looks: "..." for your project's own headers, <...> for the implementation's. And the module's own header comes first, above every other include, which is a habit with a purpose rather than a tidiness rule. It is the arrangement that proves the header is self-contained, because if stats.h needed something it had not included, this is the one place where nothing else has been included yet to cover for it.

Then main.c, which knows the module only through its header:

#include "stats.h"

#include <stdio.h>

int main(void)
{
    double readings[4] = {2.5, 3.0, 4.5, 6.0};
    struct Summary summary = summarize(readings, 4);

    printf("total %.1f, mean %.2f, after %d call\n", summary.total, summary.mean, summarize_calls);

    return 0;
}

You name every source file on the command line. Headers are never named on a build line, because a header is not compiled at all: it is pasted into the files that include it, by the preprocessor, before the compiler starts.

gcc -std=c17 -Wall -Wextra -O2 main.c stats.c -o program
total 16.0, mean 4.00, after 1 call

That single command is still chapter 1's four stages, run twice over and joined at the end. Written out it is three commands, and the split is worth seeing once, because everything that goes wrong in this lesson goes wrong at one particular stage:

gcc -std=c17 -Wall -Wextra -O2 -c main.c
gcc -std=c17 -Wall -Wextra -O2 -c stats.c
gcc main.o stats.o -o program

Each .c file is preprocessed, compiled and assembled on its own into a .o object file. The unit the compiler sees, one source file with all its #include directives already expanded, is a translation unit, and the compiler never sees two of them at once: while compiling main.c it knows nothing whatever about stats.c beyond what stats.h claimed. Joining them is the linker's job, and it works by matching names, taking the call to summarize in main.o and pointing it at the body sitting in stats.o. The compiler checks that you used a name correctly; the linker checks that the name exists exactly once. Every error left in this lesson is one of those two sentences being broken.

The Include Guard

Suppose a second module needs struct Summary in a prototype of its own, so report.h declares void print_summary(struct Summary summary); and includes "stats.h" to get the type. Now main.c includes "stats.h" for summarize and "report.h" for the printer, which is an entirely reasonable pair of lines, and the contents of stats.h land in that translation unit twice. Take the guard lines off stats.h and here is what the compiler makes of it:

In file included from report.h:1,
                 from main.c:2:
stats.h:3:8: error: redefinition of 'struct Summary'
    3 | struct Summary
      |        ^~~~~~~
In file included from main.c:1:
stats.h:3:8: note: originally defined here

Read the two "In file included from" trails, because that is the preprocessor showing you both routes by which the same header arrived. Nothing is wrong with either include; what is wrong is that a struct may be defined once per translation unit and this one was defined twice. The fix is the three lines already wrapped around stats.h above, called an include guard, and it is ordinary preprocessor arithmetic. The first time through, STATS_STATS_H is not defined, so #ifndef lets the body in and the very next line defines the macro. The second time through the macro is defined, so everything up to #endif is skipped and the header contributes nothing at all. Every header gets one, with no exceptions. Name it after the header's path so it cannot collide, STATS_STATS_H rather than a bare STATS_H, because a large project will eventually contain two files called stats.h, and two headers sharing a guard macro means the second one silently vanishes. Two spellings to avoid: a macro must not begin with an underscore followed by a capital letter, and must not contain a double underscore anywhere. _STATS_H and __STATS_H__ are both reserved for the implementation, and using a reserved identifier is undefined behaviour rather than a clash you will be warned about. You may also meet #pragma once, one line doing the same job, and gcc does support it. This course teaches the guard anyway, because #pragma once is not in the C standard while #ifndef works on every compiler that has ever existed.

Never Define Something in a Header

The rule at the top of this lesson sounds like style advice until you break it. Put a real function body in stats.h, say a double average(double total, size_t count) with braces and a return, then include that header from both main.c and stats.c. Each translation unit now contains its own complete copy of average, each one compiles perfectly, and the failure arrives one stage later:

/usr/bin/ld: stats.o: in function `average':
stats.c:(.text+0x0): multiple definition of `average'; main.o:main.c:(.text+0x0): first defined here

That is ld, the linker, not the compiler, and it names both offenders. A non-static function definition in a header is a duplicate symbol the moment two translation units include it, and since the whole point of a header is to be included by many files, that moment arrives about as soon as the module has a second caller.

Variables tell the same story with a sharper edge, because the declaration and the definition look so alike. Move int summarize_calls = 0; from stats.c up into stats.h, where two files include it, and the linker says:

/usr/bin/ld: stats.o:(.bss+0x0): multiple definition of `summarize_calls'; main.o:(.bss+0x0): first defined here

Dropping the initializer to int summarize_calls; does not buy you anything either, since on this compiler that is the identical error, so do not go hunting for a spelling that gets away with it. The pattern is the one stats.h and stats.c already showed you. Write extern int summarize_calls; in the header, which declares that the variable exists somewhere, and write int summarize_calls = 0; in exactly one .c file, which is the line that reserves the bytes. Declaration in the header, definition in one source file, for functions and variables alike.

The Other Meaning of static

Chapter 3 owed you this one. static on a local variable, it said, gives that object static storage duration so it outlives the call, and outside a function the same keyword means something unrelated that chapter 6 would explain. Here it is. On a declaration at file scope, static means internal linkage: the name is invisible to every other translation unit. Say the two side by side and never blur them. static on a local is about storage duration, when the object exists. static at file scope is about linkage, who can see the name. Same word, nothing in common but the spelling.

That is what static double total_of is doing in stats.c. It is the module's private helper, the linker is never told the name exists, and so a completely unrelated static double total_of in main.c doing something completely different is not a collision at all: that program builds and prints stats.c totalled 16.0, main.c totalled 32.0, two functions with one name in one program, each doing its own job. Delete both static keywords and it fails to link, with ld reporting total_of defined twice exactly as the header did above, which tells you what the keyword was buying. So make it your default: mark every function and file-scope variable that is not part of the module's interface as static. It prevents exactly that collision, it tells a reader that nothing outside this file can possibly be relying on the name, and it lets the compiler tell you when a private helper has stopped being used, which it cannot do for a name the whole program can reach: warning: 'halved' defined but not used [-Wunused-function]. The keyword that pairs with it needs one sentence. extern on a function declaration is redundant, because functions have external linkage already, so write struct Summary summarize(...); and never extern struct Summary summarize(...);.

Chapter 1's Error, Finished

Chapter 1 said that linker errors read differently from compiler errors, and offered undefined reference to 'helper' as one meaning the compiler was perfectly happy but nobody could find the function's body. You now have everything you need to produce it deliberately. Compile main.c and link it without ever mentioning stats.c:

/usr/bin/ld: main.o: in function `main':
main.c:(.text.startup+0x28): undefined reference to `summarize'
/usr/bin/ld: main.c:(.text.startup+0x2c): undefined reference to `summarize_calls'

Nothing about main.c is wrong. It included the header, the header declared summarize and summarize_calls, and the compiler checked every use against those declarations and had no complaint, because a declaration is a promise that the definition exists somewhere else. The linker is where that promise is collected, and no .o file on the command line had either definition in it. Which makes "undefined reference" and "multiple definition" a matched pair, the linker's only two complaints: it found the name zero times, or it found it more than once.

Key Takeaways

  • A module is a header and a source file, and headers declare while source files define. The header carries the struct definitions, prototypes and constants a caller needs; the .c file carries the bodies and everything private.
  • Each .c file is compiled alone into a translation unit and then an object file, and the linker joins them by matching names. The compiler checks that you used a name correctly; the linker checks that the name exists exactly once, which is why so many multi-file mistakes compile cleanly and fail at the link.
  • Every header gets an include guard: #ifndef STATS_STATS_H, #define STATS_STATS_H, and #endif at the bottom, named after the path so it cannot collide. Without one, a header reached by two routes is error: redefinition of 'struct Summary'. Guard names must not begin with an underscore and a capital or contain a double underscore, since those are reserved for the implementation. #pragma once does the same job, and this course uses the guard because #pragma once is not in the C standard.
  • Include your own header first in its .c file, which proves it is self-contained, and make it self-contained: a header using size_t includes <stddef.h> itself. "..." for project headers, <...> for system headers.
  • Never put a function body or a variable definition in a header. Two translation units including it is multiple definition from ld, with or without an initializer. Declare extern int counter; in the header and define int counter = 0; in exactly one .c file.
  • static at file scope means internal linkage, the name being invisible to other translation units, which is an entirely different meaning from static on a local (static storage duration). Mark everything outside the interface static: two files may then use the same private name freely, and unused helpers get diagnosed. extern on a function declaration is redundant noise.
  • undefined reference and multiple definition are the linker's two complaints: a name declared but defined nowhere, and a name defined more than once.