Declaring Global Variables with Extern in C
This article explains why global variables in C should be declared
with the extern keyword in header files and defined in a
single source file. Managing global state correctly prevents duplicate
symbol linker errors, enforces the One Definition Rule (ODR), ensures
proper memory allocation, and guarantees that all compilation units
reference the exact same memory location.
Declaration vs. Definition in C
Understanding the distinction between a declaration and a definition is essential:
- A declaration introduces a variable's name and type to the compiler without reserving memory for it. It informs the compiler that the variable exists somewhere in the program.
- A definition reserves memory for the variable and optionally initializes it. Memory can only be allocated once for any given entity.
When the extern keyword is prefixed to a variable type
in a header file (for example, extern int counter;), it
signals to the compiler that the variable is declared, not defined.
The Problem with Defining Variables in Header Files
The C preprocessor works by literally copying and pasting the
contents of an included header file into each source (.c)
file that includes it. Each source file is then compiled independently
into an object file (.o).
If a variable is defined directly in a header file without
extern:
// config.h
int max_connections = 100; // Definition (allocates storage)If both server.c and client.c include
config.h, the compiler will allocate storage for
max_connections in both server.o and
client.o. When the linker attempts to combine these object
files into an executable, it encounters two conflicting definitions for
the same symbol. This causes a linking failure commonly reported as:
multiple definition of `max_connections`
While older C standards allowed "tentative definitions" to be merged
via common blocks, modern toolchains (such as GCC 10+ using
-fno-common by default) treat duplicate definitions as
fatal errors.
The
Correct Pattern: extern in Header, Define in Source
To share a global variable safely across multiple files, split its declaration and definition across a header file and a dedicated implementation file.
1. Declare in the Header File
Declare the variable using extern in the header file. Do
not initialize it here.
// config.h
#ifndef CONFIG_H
#define CONFIG_H
extern int max_connections; // Declaration: informs compiler of type and name
#endif2. Define in One Source File
Define and optionally initialize the variable in exactly one source file. Include the corresponding header file to ensure consistency between the declaration and definition.
// config.c
#include "config.h"
int max_connections = 100; // Definition: allocates memory3. Access in Other Source Files
Any other source file that requires access to
max_connections simply includes the header file.
// main.c
#include <stdio.h>
#include "config.h"
int main(void) {
printf("Max connections: %d\n", max_connections);
return 0;
}Benefits of This Approach
- Eliminates Linker Conflicts: Because storage is allocated in exactly one translation unit, the linker encounters no duplicate symbol definitions.
- Guarantees a Single Memory Location: All parts of the program access and mutate the exact same physical memory address rather than maintaining unintended local copies.
- Faster Recompilation: Modifying initial values in
the
.cfile requires recompiling only that single translation unit, whereas changing definitions in headers triggers full rebuilds of every dependent file. - Enforces Type Safety: Including the header in the source file where the variable is defined allows the compiler to catch type mismatches between declarations and definitions at compile time.