Using extern to Share Global Variables in C
In the C programming language, managing shared state across multiple
source files requires a strict distinction between declaring a variable
and defining it. This article explains how the extern
keyword enables this separation, allowing distinct compilation units to
reference the same global variable without causing multiple-definition
errors during linking. By mastering the usage of extern,
developers can safely expose global state across an entire project while
maintaining clear ownership of data storage.
The Problem: Compilation Units and the Linker
A C program typically consists of multiple .c source
files, each referred to as a translation unit or compilation unit. When
compiling a project, the compiler processes each compilation unit
independently into an intermediate object file (.o or
.obj). Because the compiler operates on one unit at a time,
it has no inherent knowledge of variables defined in other files.
If you place a standard global variable definition—such as
int global_counter = 0;—inside a shared header file and
include that header in multiple .c files, each compilation
unit attempts to allocate storage for global_counter. While
compilation will succeed, the linker will fail with a "multiple
definition" or "duplicate symbol" error because it encounters two or
more conflicting memory allocations with the same identifier.
Declarations vs. Definitions
Understanding extern requires understanding the
difference between a declaration and a definition:
- Definition: Instructs the compiler to reserve memory space for the variable. A variable can only be defined once across the entire linked program (abiding by the One Definition Rule).
- Declaration: Informs the compiler about the variable's name and data type, but does not allocate memory. It asserts that the actual storage exists somewhere else.
The extern keyword converts a variable statement into a
pure declaration (provided it is not given an initializer).
How extern
Resolves Shared Variables
When you prefix a variable declaration with extern, you
instruct the compiler not to allocate storage in the current translation
unit. Instead, the compiler generates a symbolic reference and places an
unresolved symbol entry in the generated object file.
During the final build step, the linker scans all object files to match these unresolved references with the single translation unit that contains the actual definition. Once matched, the linker replaces the unresolved references with the actual memory address of the defined variable.
Standard Implementation Pattern
The standard and safest way to share a global variable across multiple files involves three steps:
1. Declare the variable in a header file
Place an extern declaration inside a common header file.
This makes the variable's name and type available to any file that
includes the header.
/* counter.h */
#ifndef COUNTER_H
#define COUNTER_H
extern int shared_counter;
#endif2. Define the variable in one source file
Allocate the actual storage in exactly one .c file. This
file should also include the header to allow the compiler to verify that
the definition matches the declaration.
/* counter.c */
#include "counter.h"
int shared_counter = 0; // Definition and memory allocation3. Access the variable in other source files
Any other compilation unit can now include the header file and read or modify the variable directly.
/* main.c */
#include <stdio.h>
#include "counter.h"
int main(void) {
shared_counter += 5;
printf("Counter value: %d\n", shared_counter);
return 0;
}Important Considerations
- Avoid Initializers with
extern: Writingextern int x = 10;overrides the declaration status and turns the statement into a definition. Doing this in a header file reintroduces duplicate definition errors. - Default Linkage: Variables defined outside of any
function have external linkage by default. Adding the
statickeyword restricts a global variable's scope exclusively to its own compilation unit, preventingexternfrom accessing it from other files.