C Modular Architecture, Header Files, Linkage & Multi-File Compilation Masterclass
Welcome to Phase 15 (Chapter 41): C Modular Architecture, Headers, Linkage & Compilation Units Masterclass! Real-world C programs are never written in a single file. In this guide you learn how to split C projects across multiple .c source files and .h header files, understand symbol linkage, and manage the compilation pipeline professionally.
A Translation Unit (TU) is one .c source file PLUS all the header files it recursively includes via #include. Each .c file is independently preprocessed and compiled into one .o object file. The linker then combines all object files into the final executable.
| Qualifier | Scope | Meaning |
|---|---|---|
extern | Global (External Linkage) | Symbol is visible across ALL translation units. Declared in header, defined ONCE in one .c file. |
static (file scope) | Local (Internal Linkage) | Symbol is invisible outside its own .c file. Use for helper functions you want to hide. |
| (no qualifier) | Global (External Linkage) | Same as extern β visible to linker from all TUs. |
What belongs in a .h header file:
β
Function declarations (prototypes): int add(int a, int b);
β Type definitions (typedef, struct, enum declarations)
β
Macro and constant definitions (#define PI 3.14159)
β
extern variable declarations: extern int global_counter;
β Function definitions (actual body code) β causes duplicate symbol linker errors!
β Unguarded global variable definitions β causes ODR (One Definition Rule) violations!
/* math_utils.h */
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
/* Public API declarations */
int add(int a, int b);
int multiply(int a, int b);
double power(double base, int exp);
#endif /* MATH_UTILS_H */
/* math_utils.c */
#include "math_utils.h"
/* Private helper β internal linkage, invisible outside this file */
static int validate(int x) { return x >= 0 ? x : -x; }
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
double power(double base, int exp) {
double result = 1.0;
for (int i = 0; i < exp; i++) result *= base;
return result;
}
/* main.c */
#include <stdio.h>
#include "math_utils.h" /* Include INTERFACE, not implementation! */
int main(void) {
printf("3 + 4 = %d\n", add(3, 4));
printf("3 * 4 = %d\n", multiply(3, 4));
printf("2^10 = %.0f\n", power(2, 10));
return 0;
}
/* Compile: gcc -Wall -Wextra math_utils.c main.c -o calculator */Q1: What is a "duplicate symbol" linker error?
It occurs when the same function or global variable is defined (not just declared) in more than one .c file. The linker finds two implementations and does not know which to use.
Q2: Why use static for file-scope helper functions?
static functions have internal linkage β they cannot be called from other .c files, enabling compiler optimizations and preventing accidental API usage.
Q3: What is the One Definition Rule (ODR)?
Every function and global object must have exactly one definition across all translation units in a program. Multiple definitions cause linker errors.
Q4: How do I share a global variable across files?
Declare it with extern int g_count; in the header. Define it ONCE in one .c file: int g_count = 0;. Include the header everywhere else.
Q5: What is a forward declaration?
A forward declaration tells the compiler about a symbol's type and name without providing its full definition, allowing circular references to be resolved.