Why this matters
As soon as a program has several files, names start to collide. Two modules both want a helper called init or a counter called count. Some variables genuinely need to be shared between files, like a configuration setting; most should be private to the file that owns them. C controls this with linkage, and two keywords, extern and static. Getting linkage right is the difference between modules that can be combined freely and a program where adding a file breaks the build with "multiple definition".
Three kinds of linkage
Every name in C has a linkage, which answers: "if the same name appears in another place, is it the same thing?"
| linkage | meaning | typical examples |
|---|---|---|
| external | the same name in any file of the program refers to the same object or function | ordinary functions; file-scope variables without static |
| internal | the same name refers to the same thing only within this translation unit | functions and file-scope variables declared static |
| none | each declaration is its own separate thing | local variables, function parameters |
The defaults:
- A function has external linkage unless declared
static. - A variable declared outside any function has external linkage unless declared
static. - A variable declared inside a function has no linkage.
The linker only works with names that have external linkage. Internal names never leave the object file as global symbols, which is why they cannot clash.
static at file scope: making things private
Here is a module with a public function, a shared variable, and private details:
counter.h:
#ifndef COUNTER_H
#define COUNTER_H
extern int counter_calls; /* declaration: defined in counter.c */
int counter_next(void);
#endif
counter.c:
#include "counter.h"
int counter_calls = 0; /* definition, external linkage */
static int current = 100; /* internal linkage: private to this file */
static int step(void) /* private helper */
{
return 5;
}
int counter_next(void)
{
counter_calls++;
current += step();
return current;
}
main.c, which has its own, unrelated step:
#include <stdio.h>
#include "counter.h"
static int step(void) /* a different, unrelated step */
{
return 1;
}
int main(void)
{
for (int i = 0; i < 3; i += step())
printf("next: %d\n", counter_next());
printf("counter_next was called %d times\n", counter_calls);
return 0;
}
$ gcc -std=c17 -Wall -Wextra -c counter.c main.c
$ nm counter.o
0000000000000000 B counter_calls
000000000000000b T counter_next
0000000000000000 d current
0000000000000000 t step
$ nm main.o
U counter_calls
U counter_next
000000000000000b T main
U printf
0000000000000000 t step
$ gcc counter.o main.o -o demo && ./demo
next: 105
next: 110
next: 115
counter_next was called 3 times
Read the nm letters: the letter is the section (T code, D initialized data, B data initialized to zero, U undefined here) and the case is the linkage. Upper case is global, lower case is local. Both files have a lower-case t step; the linker ignores local symbols when matching names, so the two step functions coexist peacefully. Without static on both, the link fails:
/usr/bin/ld: /tmp/ccevSTk5.o: in function `helper':
h2.c:(.text+0x0): multiple definition of `helper'; /tmp/ccyMGnPd.o:h1.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
The rule of thumb: make every function and file-scope variable static unless another file needs it. Public names belong in the header; everything else is static. This is C's version of "private", and it also helps the compiler, which can inline or remove a static function it knows nobody else calls. gcc even warns about an unused static function (defined but not used), which it cannot do for external ones.
extern: sharing one variable across files
To share a variable, you need exactly one definition (which allocates the storage and may initialize it) and any number of declarations (which say "this exists somewhere, with this type"):
int counter_calls = 0; /* definition: in exactly one .c file */
extern int counter_calls; /* declaration: in the header, seen by everyone */
counter.h: extern int counter_calls; ---- included by ----> main.c, counter.c, ...
counter.c: int counter_calls = 0; ---- the one real object in memory (.bss)
main.o: U counter_calls --\
>-- linker points both at the same 4 bytes
counter.o: B counter_calls --/
extern without an initializer never allocates storage. If no file defines the variable, you get the familiar linker error:
e.c:(.text+0x6): undefined reference to `limit'
If two files define it, you get multiple definition. With GCC 10 and later this includes definitions without initializers: int count; in two files is an error. (Older compilers merged such "tentative definitions" silently, which is why some old code relies on it and now fails to link; -fcommon restores the old behavior, but fixing the code is better.)
Functions are extern by default, so extern int counter_next(void); and int counter_next(void); mean the same thing. People usually leave out extern on function prototypes and always write it on variable declarations.
Shared globals: use sparingly
A global variable with external linkage can be changed by any code in the program, which makes behavior hard to reason about and tests hard to write. Prefer, in order:
- A
staticvariable in one file, accessed through functions (set_verbosity(3),get_verbosity()). The module controls every change and can validate it. - Passing values as parameters, or in a struct that holds the program's configuration.
- An
externglobal, when it really is program-wide and simple, like a verbosity flag orerrnoitself.
static inside a function: a different meaning
The same keyword has a second, unrelated job. Inside a function, static changes a local variable's storage duration, not its linkage:
int next_ticket(void)
{
static int n = 0; /* initialized once, before main; keeps its value */
return ++n;
}
Each call returns 1, 2, 3, ... because n lives in the data segment like a global, not on the stack, but its name is only visible inside the function. You met this in the functions module. The two uses of static are easy to mix up:
| where | what static changes | effect |
|---|---|---|
| file scope (outside functions) | linkage: external to internal | the name is private to this file |
| block scope (inside a function) | storage duration: automatic to static | the variable survives between calls |
Common mistakes
- Defining a variable in a header (
int verbosity = 1;). Every file that includes it defines it:multiple definition. Putextern int verbosity;in the header and the definition in one.cfile. - Declaring with
externeverywhere and defining nowhere:undefined reference. - Leaving helpers non-static, so they clash with another module's helper of the same name, or get called from places they were never meant to be.
- Declaring a function
staticin the.cfile but prototyping it in the header. Other files then try to call a function they cannot see:undefined reference, or, inside the same file,static declaration of 'f' follows non-static declaration. - Mismatched types between the
externdeclaration and the definition, such asextern long limit;in one file andint limit;in another. If the defining file does not include the header, nobody notices, and reads go past the end of the variable. Include the header in the defining file. - Expecting
staticto mean constant. That isconst.
Recap
- Linkage decides whether the same name in different places means the same thing: external (whole program), internal (one file), or none (locals).
- Functions and file-scope variables are external by default;
staticat file scope makes them internal, which is how C keeps module details private. - A shared variable has one definition in a
.cfile andexterndeclarations (in a header) everywhere else. nmshows linkage by case:T/D/Bglobal,t/d/blocal,Uneeded from elsewhere.staticinside a function means "keep this variable alive between calls", a different job.
Going further: run nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep ' T ' | wc -l in the sandbox to count the functions libc exports. Then check that nm on one of your own programs shows your static helpers in lower case.