Storage classes and linkage
Pointers and memory11 min readLesson 6 of 6
6.1 Scope, lifetime, and linkage
Three properties describe a name in C, and they answer different questions. Scope is the region of source where the name is visible. Lifetime is when the object exists while the program runs. Linkage is whether other source files can refer to the same name.
A local variable has block scope and automatic lifetime, and no linkage. A variable at file scope has static lifetime and external or internal linkage depending on the keyword.
Most confusion comes from mixing the three. A static local and a static global share a keyword and almost nothing else.
#include <stdio.h>
int grand_total = 100;
static void add_to_total(int amount)
{
grand_total += amount;
}
int main(void)
{
int local = 5;
add_to_total(local);
printf("local: %d\n", local);
printf("global: %d\n", grand_total);
return 0;
}Output
local: 5 global: 105
6.2 auto is the default
auto marks a variable with automatic storage duration. Inside a function it is the default, so nobody writes it. In C it cannot appear at file scope.
Automatic variables are created when their block is entered and destroyed when it ends. The initial value is undefined unless you provide one, and reading an uninitialised local is a common source of unpredictable results.
Initialising at the declaration is the habit to build. int count = 0; costs nothing and removes a whole class of bugs.
#include <stdio.h>
static int sum_to(int limit)
{
auto int total = 0;
for (int i = 1; i <= limit; i++) {
total += i;
}
return total;
}
int main(void)
{
printf("%d\n", sum_to(4));
return 0;
}Output
10
6.3 static inside a function
A static local keeps its value between calls. It is created once, before main runs, and lives until the program ends. The name still has block scope, so no other function can see it.
Use it for a counter, a cached result, or a table that is expensive to build. The initialiser must be a constant expression, and it runs only once.
A static local is shared by every caller and every thread. In concurrent code it needs a lock or a different design. For a single-threaded exercise it avoids a global.
#include <stdio.h>
static int next_id(void)
{
static int id = 100;
return id++;
}
int main(void)
{
printf("%d\n", next_id());
printf("%d\n", next_id());
printf("%d\n", next_id());
return 0;
}Output
100 101 102
6.4 static at file scope
At file scope, static changes linkage instead of lifetime. The name becomes internal: other source files in the same program cannot refer to it.
That is how a helper stays private to its file. Without static, two files that define the same helper name produce a multiple definition error at link time.
A translation unit is one .c file after preprocessing. Every function and global in it is a candidate for internal linkage unless another file needs it.
#include <stdio.h>
static int helper_calls = 0;
static int double_it(int value)
{
helper_calls++;
return value * 2;
}
int main(void)
{
printf("%d\n", double_it(21));
printf("helper calls: %d\n", helper_calls);
return 0;
}Output
42 helper calls: 1
6.5 extern declares without defining
extern says the object exists somewhere else. A declaration without a definition allocates nothing, and the linker resolves the name later.
Globals are declared in a header as extern int shared_total; and defined in exactly one .c file as int shared_total = 0;. Every file that includes the header compiles against the declaration.
Function declarations are extern by default, so adding the keyword to a prototype changes nothing.
#include <stdio.h>
int counter = 0;
extern int counter;
static void bump(void)
{
counter++;
}
int main(void)
{
bump();
bump();
printf("counter: %d\n", counter);
return 0;
}Output
counter: 2
6.6 register is a hint nobody needs
The register keyword asks the compiler to keep a variable in a register. Compilers treat it as a hint and ignore it whenever they have a better plan.
One restriction remains: you cannot take the address of a register variable. That rule was useful in the 1970s and is a nuisance now.
Leave the keyword out. If performance matters, measure first, then adjust the algorithm or the data layout.
#include <stdio.h>
int main(void)
{
register int i = 0;
for (; i < 4; i++) {
printf("%d\n", i);
}
return 0;
}Output
0 1 2 3
6.7 Choosing a storage class
Start with a local variable. Move it to static when the value must persist between calls, and to file scope when several functions in one file share it. Add extern only when another file needs it.
Globals make data flow invisible. A function that reads a global takes no parameter and returns no value, so its dependencies do not appear in its signature.
When you do use a global, give it a name that says what it holds, declare it in one header, and keep it static unless another file genuinely needs access.
#include <stdio.h>
static const int limits[] = {10, 20, 30};
static int limit_for(int index)
{
static const int count = 3;
if (index < 0 || index >= count) return -1;
return limits[index];
}
int main(void)
{
printf("%d %d %d\n", limit_for(0), limit_for(1), limit_for(2));
return 0;
}Output
10 20 30
