58 Where a name may be used — scope
What to know first
Looking back
Chapter 25 said “a name declared inside a block is visible only inside that block”. How many names called n are there in this code?
for (int n = 0; n < 3; n++) { }
for (int n = 10; n > 8; n--) { }A. Two, and they have nothing to do with each other. Sameness of spelling is not sameness of name: two names are the same only when they are the same spelling in the same region. This chapter chases that region to the end — how many kinds there are, where each begins and ends, and what happens where they overlap.
The need for this chapter, and its context
By the end of this chapter
The questions this chapter answers
- Why are labels the exception?
58.1 What a scope is — not “where a name is visible” but “where it means that thing”#
Scope was introduced as “the region in which a name is visible”. More precisely, it is the region of the program in which a given identifier is established as denoting a particular thing. The same letters can denote something else in another region, or nothing at all.
So the question to ask is not “does this name exist?” but “what does this name mean here?”. That distinction runs through the whole chapter.
Scope is a property of the name, not of the object. An object can be perfectly alive where its name is not visible (chapter 45), and conversely a name can be visible while the object does not exist yet, or no longer does.
58.2 The four scopes#
The standard divides scope into four (C23 §6.2.1). Three are met constantly; the fourth causes misunderstandings if you have not met it.
| scope | from where | to where | what lives here |
|---|---|---|---|
| file scope | the point of declaration | the end of the translation unit | everything declared outside functions |
| block scope | the point of declaration | that block’s closing brace | locals, parameters, for’s first slot |
| function prototype scope | the parameter name | the prototype’s closing parenthesis | parameter names in a prototype |
| function scope | anywhere in the function | the whole function | labels only |
Table 58.1 — The four scopes the standard defines
examples-en/ch56/scopes.c
/* The four scopes a name can have --- exactly as the standard defines them. */
#include <stdio.h>
/* (1) file scope: visible from here to the end of this file */
static int file_level = 1;
/* (2) function prototype scope: the parameter names rows and cols vanish when
this parenthesis closes. They are useful even so --- the size of a later
parameter is written with them. */
void print_grid(int rows, int cols, int grid[rows][cols]);
/* (3) function scope: a label is visible throughout the function, even one written inside a block. */
static int find_first_negative(const int *a, int n)
{
for (int i = 0; i < n; i++) {
if (a[i] < 0) {
goto found; /* jump to a label inside the block below */
}
}
return -1;
{
found: /* inside a block, yet visible anywhere in the function */
return 0;
}
}
void print_grid(int rows, int cols, int grid[rows][cols])
{
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) printf(" %d", grid[r][c]);
putchar('\n');
}
}
int main(void)
{
/* (4) block scope: only inside these braces */
int block_level = 2;
{
int inner = 3;
printf("inner block sees: file=%d block=%d inner=%d\n",
file_level, block_level, inner);
}
/* out here, inner is no longer a name */
int grid[2][3] = {{1, 2, 3}, {4, 5, 6}};
puts("\nthe grid, sized by names from the prototype scope:");
print_grid(2, 3, grid);
int values[] = {5, 7, -2, 9};
printf("\nfirst negative found: %s\n",
find_first_negative(values, 4) == 0 ? "yes" : "no");
return 0;
}
Output
inner block sees: file=1 block=2 inner=3
the grid, sized by names from the prototype scope:
1 2 3
4 5 6
first negative found: yes
The demonstration shows all four at once. Two of them deserve a second look.
Function prototype scope is useful even though the name vanishes at once. A parameter name written in a prototype disappears when the parenthesis closes — which is why void f(int n); and void f(int); are exactly the same declaration. There are two reasons to write the name anyway: for the human reader, and to express the size of a later parameter with it.
void print_grid(int rows, int cols, int grid[rows][cols]);rows and cols are used to spell the shape of the third parameter and then vanish. Without names this notation could not be written at all (chapter 40).
Only labels have function scope. The found: in the demonstration sits inside an inner block, yet is visible from anywhere in the function. That is what lets goto jump across block boundaries — and it is a privilege of labels alone. Variables never behave this way.
Q. Why are labels the exception?
A. Because a label has no value. The real reason a variable’s name is confined to its block is that the object it names comes and goes with the block; what a label names is a point in the code, which has nothing to come or go. So there is nothing to confine.
★ Still, “can jump” and “may jump” are different. Jumping into a block skips the declarations in it, and jumping into a block that contains a variable-length array is forbidden outright.
58.3 Where a scope begins — before the equals sign#
When a name is declared, its scope begins where the declarator ends — which is before the = of an initialiser. It sounds like a detail; the consequences are not.
examples-en/ch56/scope_start.c
/* A scope begins where the declarator ends --- before the equals sign, not after. */
#include <stdio.h>
typedef int meter; /* a type name at file scope */
int main(void)
{
/* i's scope has already begun *before* the = is reached, so sizeof(i)
refers to the i just declared (and does not read its value). */
int i = sizeof(i);
printf("int i = sizeof(i); -> %d\n", i);
/* a pointer holding its own address works for the same reason */
void *self = &self;
printf("void *self = &self; -> self == &self is %s\n",
self == (void *)&self ? "true" : "false");
{
/* Here meter is a type name. After the line below, meter is a
*variable* in this block --- from where the declarator ended. */
meter meter = 42;
printf("meter meter = 42; -> %d (the type name is now shadowed)\n", meter);
}
/* outside the block, meter is a type name again */
meter distance = 7;
printf("outside the block, meter is a type again -> %d\n", distance);
/* a name declared in for's first slot is gone when the loop ends */
for (int n = 0; n < 3; n++) { /* n lives only here */ }
/* out here n is not a name --- reusing it means nothing to the first one */
for (int n = 10; n > 8; n--) { /* unrelated to the n above */ }
puts("\ntwo loops used the name n; neither knows the other");
return 0;
}
Output
int i = sizeof(i); -> 4
void *self = &self; -> self == &self is true
meter meter = 42; -> 42 (the type name is now shadowed)
outside the block, meter is a type again -> 7
two loops used the name n; neither knows the other
That is why int i = sizeof(i); gets through. By the time the right-hand side is reached, i already means “the int just declared”, and sizeof measures without reading, so nothing is amiss. void *self = &self; is sound for the same reason: taking an address needs no value.
Counter-example. The dark side of the same rule
int x = 10;
{
int x = x + 1; /* <- not the outer x. Itself. */
}The inner x’s scope began before the =, so the x on the right is not the outer 10 but itself, with no value yet. The value is read, so this is undefined behaviour (chapter 54). Compilers usually say so under -Winit-self or -Wuninitialized, but with the warnings off it passes in silence.
The cure is not to memorise the rule but to pick a different name.
Type names follow the same rule. The meter meter = 42; in the demonstration is the proof: the first meter is still a type name, and from the end of the declarator onwards meter is a variable in that block.
58.4 Shadowing — the inner covers the outer#
Scopes may overlap. Declaring a name in an inner block that matches one outside makes the name mean the inner one inside that block. This is shadowing.
examples-en/ch56/shadow.c
/* Shadowing --- and the fact that the preprocessor knows nothing of scope. */
#include <stdio.h>
int count = 100; /* file scope */
static void layers(void)
{
printf("file scope : count = %d\n", count);
int count = 10; /* shadows the file-scope count */
printf("function body : count = %d\n", count);
{
int count = 1; /* shadows it again */
printf("inner block : count = %d\n", count);
}
printf("back in the body : count = %d\n", count);
/* a shadowed name is not gone --- it is merely covered for a while */
}
static void define_inside(void)
{
/* This directive has nothing to do with being "inside a function".
Preprocessing finishes before compiling, and it knows no blocks. */
#define LIMIT 5
printf("\ninside the function : LIMIT = %d\n", LIMIT);
}
static void far_away(void)
{
/* A different function, and LIMIT is still alive --- its reach is not a
scope but "from that point to the end of the file". */
printf("another function : LIMIT = %d\n", LIMIT);
}
int main(void)
{
layers();
define_inside();
far_away();
printf("\nthe file-scope count is still %d\n", count);
return 0;
}
Output
file scope : count = 100
function body : count = 10
inner block : count = 1
back in the body : count = 10
inside the function : LIMIT = 5
another function : LIMIT = 5
the file-scope count is still 100
A shadowed name does not disappear. It has merely yielded its spelling for a while, and it returns intact when the block ends — as the last line of the demonstration confirms.
The difficulty is that this is legal. C does not forbid shadowing, and for good reason: if it did, whether a local variable name compiled would depend on which global names happen to be in somebody else’s header. Forbidding it would break the encapsulation of names.
The discipline therefore lives in tools and habits rather than in the language.
| means | what it buys |
|---|---|
-Wshadow | warns at every declaration that covers an outer name |
| declaring narrowly | declare at the point of use and there is less to cover |
| naming | long on the outside (config_limit), short on the inside (i, n) |
Table 58.2 — Working discipline for shadowing
What -Wshadow says looks like this.
warning: declaration of 'count' shadows a global declaration [-Wshadow]★ It is not on by default, and turning it on tends to make other people’s headers noisy. Enabling it for your own code only is the practical compromise.
58.5 The preprocessor knows nothing of scope#
The contrast that surprises people most often is this one. The #define LIMIT 5 in the demonstration is written inside a function, and yet it is not confined to that function: it is still alive in another one.
The reason is simple. The layer we will open in chapter 61 finishes before compilation, and it knows neither blocks nor functions. What a macro has is not a scope but a stretch — from the point of definition until #undef or the end of the file.
| a C name | a macro name | |
|---|---|---|
| what bounds it | block structure | from its definition to the end of the file |
| can an inner one cover it | yes | no — a clash simply replaces it |
| how it ends | close the block | #undef |
Table 58.3 — Two layers make names, by two different rules
★ So the convention of spelling macros in capitals is not a matter of taste but a safety device. These names get no protection from scope, so they are made conspicuous for humans to avoid.
58.6 Scope, lifetime, linkage — three that get confused#
Finally, the three side by side. They are especially easy to mix up because the single word static means different things in different places.
| axis | the question | what settles it |
|---|---|---|
| scope | what does this name mean here | block structure (this chapter) |
| lifetime | when does the object exist, from when to when | storage duration (chapter 45) |
| linkage | is it the same as that name in another unit | static, extern (chapter 56) |
Table 58.4 — Three questions about a name and its object
The three axes are independent, so all of these combinations exist.
static int n;inside a function — block scope, static lifetime, no linkagestatic int n;at file level — file scope, static lifetime, internal linkageint n;at file level — file scope, static lifetime, external linkageint n;inside a function — block scope, automatic lifetime, no linkage
A common misconception. If the name is not visible, the object is not there
No. A static variable inside a function loses its visible name the moment the function returns, yet the object stays and its value is still there on the next call. Conversely, hand out an address and the object can be touched from places its name never reaches — which is exactly what pointers are for.
★ The dangerous combination is therefore the place where the name is gone and so is the object: holding on to the address of a local after leaving its block is the classic case (chapter 45).
We can now say precisely where a name holds. The next chapter asks a different question: there are names that share a place and a spelling and still do not collide, because C has four separate yards for names to live in.