Skip to main content

Scope, Linkage, and Storage Classes

What This Concept Is

Every name in a C program has three orthogonal attributes:

  • Scope: where in the source the name is visible. Block scope (inside {}), function prototype scope, file scope (outside any function).
  • Linkage: whether the name refers to the same entity across translation units.
    • extern linkage - the name is the same symbol across all files that declare it (e.g., printf).
    • Internal linkage - the name is a distinct entity in this file only (via static at file scope).
    • No linkage - block-scope names, function parameters, typedef names.
  • Storage duration: how long the object exists.
    • Automatic - lives during the enclosing block.
    • Static - lives for the whole program, initialized once.
    • Allocated - lives from malloc to free (not a storage-class specifier; covered in Module 2).
    • Thread - lives for the thread (C11 _Thread_local).

The storage-class specifiers auto, register, static, extern, and _Thread_local pick pieces of these.

Why It Matters Here

Once you have multi-file programs, these attributes decide:

  • whether two files can see the same global variable or function
  • whether a helper function is accidentally exported to the whole program
  • whether a static local variable remembers its value between calls

Misunderstanding any one of these produces hard bugs, silent duplications, or giant symbol tables.

Concrete Example

/* util.c */
#include "util.h"

static int call_count = 0; /* internal linkage: only this file sees it */

static int helper(int x) { /* internal linkage: private helper */
return x * 2;
}

int public_add(int a, int b) { /* external linkage: declared in util.h */
call_count++;
return helper(a) + helper(b);
}

int get_call_count(void) {
return call_count;
}
/* counter.c */
int counter_inside(void) {
static int n = 0; /* static storage, block scope, no linkage */
n++;
return n; /* survives across calls */
}

static means three different things here: file-scope static int call_count (internal linkage), block-scope static int n (static storage duration), file-scope static int helper(...) (internal linkage for a function). The keyword is overloaded.

Common Confusion / Misconception

"static means constant." No. static is about either storage duration (keeps value across calls) or linkage (hides from other files). Constants use const.

"extern int x; defines x." No. It declares that x exists somewhere. The definition is the single int x = 0; (or int x;) at file scope in exactly one .c file.

"register makes it faster." In practice no. Modern compilers ignore register as an optimization hint. Its only remaining effect is that you cannot take the address of a register variable.

How To Use It

  1. Mark file-scope helpers static unless they need to be callable from other files.
  2. Put extern declarations of shared globals in a header; put the single definition in one .c file.
  3. Use block-scope static for small per-function caches (e.g., a one-time initialization flag).
  4. Avoid register. Write clear code and trust the optimizer.
  5. Treat any non-static file-scope variable as part of your module's public surface area.

Check Yourself

  1. What does static mean on a file-scope function? On a block-scope variable?
  2. What is the difference between extern int x; in a header and int x; in a .c file?
  3. If two .c files both define int foo; at file scope without static, what happens at link time?

Mini Drill or Application

Write a two-file program that:

  1. Declares int request_count; with external linkage in stats.c and exposes it via extern int request_count; in stats.h.
  2. Uses a static file-scope function static void normalize(char *s) inside stats.c that is not visible to main.c.
  3. Uses a block-scope static int last_id = 0; inside a function int next_id(void) to give each call a new id.
  4. Confirm with nm stats.o that the static helpers are local and the public function is global.

Read This Only If Stuck