Linkage: internal, external, none; extern, static
This chapter explains linkage — how identifiers with the same name in different translation units (or the same one) refer to the same or different entities. It covers extern and static in their storage-class roles.
Why This Matters
Linkage is what makes multi-file programs work. Without a precise model of internal vs. external linkage, you cannot correctly share globals across files or keep implementation details private.
Prerequisites
c.core.6— declarations and definitions.c.core.34— inline functions (interacts with linkage).
Core Concept
What linkage is
An identifier's linkage determines whether it refers to the same entity as an identifier of the same name in another scope or translation unit.
| Linkage | Meaning |
|---|---|
| External | Visible across the whole program; refers to the same entity in all TUs |
| Internal | Visible only within one translation unit |
| None | No linkage (e.g., block-scope locals, labels, typedefs) |
The two storage-class keywords
externdeclares an identifier with external linkage (or refers to an
already-declared external identifier).
staticat file scope gives an identifier internal linkage (private to
the TU); at block scope it gives static storage duration.
Syntax
/* external linkage */
extern int shared;
int shared = 0; /* definition with external linkage */
/* internal linkage */
static int private; /* visible only in this TU */
/* none (block scope, no linkage) */
int local;
Examples
Sharing a global across files
/* counter.h */
extern int counter; /* declaration: the definition is elsewhere */
/* counter.c */
int counter = 0; /* the one external definition */
/* main.c */
#include "counter.h"
#include <stdio.h>
int main(void) {
printf("%d\n", counter);
return 0;
}
Keeping a helper private
/* util.c */
static int helper(int x) { return x * 2; } /* internal linkage */
int public_api(int x) { return helper(x); } /* external linkage */
helper is not visible outside util.c.
static at block scope
void f(void)
{
static int calls = 0; /* static storage, but NO linkage */
calls++;
}
The block-scope static has no linkage but static storage duration.
How It Works
The compiler records each identifier's linkage in the symbol table. External symbols are exposed to the linker; internal symbols are kept local to the object file; no-linkage identifiers are purely local to a function/block and may not even get a symbol.
Variations
Tentative definitions and linkage
At file scope, int x; without extern is a tentative definition with external linkage (unless later declared static). If no other definition appears, it becomes a definition.
static functions
A static function has internal linkage and is callable only within its TU. This is the standard way to make private helper functions.
Multiple declarations
You can declare the same external identifier many times; the declarations must be compatible. Exactly one definition may exist (or one tentative definition that becomes a definition).
Common Mistakes
- Forgetting
externon a shared global declaration in a header, causing a
definition in every includer (duplicate definition).
- Confusing block-scope
static(storage duration) with file-scopestatic
(linkage).
- Using a
staticfunction from another file (undefined reference).
Undefined Behavior
- Declaring the same identifier with incompatible types across TUs is a
constraint violation and, if undiagnosed, UB.
- Defining an external identifier more than once is a constraint violation.
Portability
- Linkage rules are standard and portable.
- The precise way the linker resolves symbols is platform-specific, but the
language-level guarantees are universal.
Under the Hood
External symbols appear in the object file's symbol table with global binding; internal symbols have local binding; no-linkage identifiers are typically stack-relative or register-allocated and have no symbol. The linker merges global symbols and errors on duplicates.
Practical Usage
- Declare shared globals
externin headers; define them once in a.cfile. - Mark file-private functions and variables
static. - Prefer functions over shared mutable globals to minimize coupling.
Exercises
1. Create three files: a header with extern int x;, a .c file defining x, and a main using it. Build and run. 2. Add a static function to a file and try to call it from another file; observe the linker error. 3. Explain the difference between static int x; at file scope and inside a function.
Deep Challenge
Explain why extern int x; in a header plus int x; in exactly one .c file is correct, but int x; in the header (without extern) is a bug. Describe the exact linker diagnostic and how tentative definitions interact.
Related Concepts
c.core.6— declarations/definitions.c.core.39— multi-file programs and headers.c.lang.storage-duration— static storage duration.
References
- ISO/IEC 9899:2018 §6.2.2 (linkages), §6.7.1 (storage-class specifiers).
Verification
- External/internal/no linkage definitions.
VERIFIED staticat file scope gives internal linkage.VERIFIEDstaticat block scope gives static duration, no linkage.VERIFIED- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Linkage categories
- [ ] extern
- [ ] static (file scope)
- [ ] static (block scope)
- [ ] Tentative definitions
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.lang.linkage | 0 | 6 |
| c.lang.extern | 0 | 5 |
| c.lang.static | 0 | 5 |