56 Building out of several files — translation units and linkage
What to know first
Looking back
Chapter 25 said “write prototypes at the top of the file and the definitions may be anywhere below — and, more importantly, they may be in another file.” Explaining the grounds for that with chapter 17′s relay?
A. Because the compilation stage works from declarations alone, and it is the linker that finds and joins the bodies (chapter 17). So when compiling one file the contents of other files are not needed at all — all that is needed is the promise that “a function of this name with this signature exists somewhere”, and the vessel that carries that promise is the header file. This chapter sees that division of labour in the flesh.
The need for this chapter, and its context
By the end of this chapter
The questions this chapter answers
- How are link error messages read?
56.1 Translation units and headers#
One source file after preprocessing — the result of all the #includes being spread out — is called a translation unit. The compiler always sees one translation unit at a time. A multi-file program means making several translation units into object files separately and having the linker join them into one.
We see the division of labour in a three-file example.
examples-en/ch54/main.c
/* A multi-file example — this file compiles seeing only greet's declaration. */
#include "greet.h"
int main(void)
{
greet("world");
printf_count();
return 0;
}
Output
Hello, world!
greet was called 1 times
greet.h (the header) contains only the declaration — the contract signature. greet.c has the definition, and main.c includes only the header, knows the contract and calls. Compilation happens separately for each, and the linker joins the call to greet with its definition.
The header’s #ifndef GREET_H ... #define ... #endif is an include guard. It is the idiom that makes the contents unfold only once even if the header is attached twice through different paths, preventing things that error when declared twice (struct definitions and the like). In practice it is often replaced by the single line #pragma once (not standard, but supported by all the major compilers).
56.2 Linkage — does a name cross file boundaries?#
A name has one more property besides scope (chapter 25) — linkage, that is, whether that name is visible from another translation unit.
- External linkage: visible outside the file. The default for functions and global variables.
- Internal linkage: visible only within this translation unit. Attach
staticto a file-level declaration — the example’sstatic int callsis the case.
That the word static is used with different meanings in chapter 45 (static lifetime) and here (internal linkage) is C’s famous word recycling — inside a function static means duration, at file level it means linkage.
Internal linkage is a basic weapon in practice. C gives the programmer no way to dig a new name space (chapter 59), so every external name meets in one yard; attach static to helper functions and variables used only inside a file and those names never go out into the yard at all — no collisions, and the compiler can optimise more aggressively (knowing that name cannot be called from another file — an honest signal to chapter 14′s editor). This weapon and the rest of the defences against name collisions are the subject of chapter 60.
A common misconception. “Putting a function definition in a header is convenient, so it is fine”
static inline functions and macros are conventionally put in headers, and so are C23′s constexpr constants. Knowing the exception and using it differs from breaking the rule in ignorance.)Q. How are link error messages read?
A. Two sentences solve most of it. “undefined reference to X” — the declaration was seen but the definition could not be found (chapter 17). A source file was left out of the build, a library was not joined, or the spelling differs. “multiple definition of X” — there are two or more definitions. Common when a definition was put in a header, or a global variable was declared and initialised in a header. That both messages come from the linker, not the compiler is the starting point of the diagnosis — it means the syntax was fine.
This chapter was about how source is divided. Putting the pieces back together — what the linker actually does, and what the joined result promises to the world outside — is the next chapter.