Skip to content
VLSI Mentor

C Part I · Module 5 · Functions and Program Decomposition

Scope: Where a Name Is Visible

Two chapters have now deferred the same question. Chapter 5.2 said that parameter names are private to the function and did not say why; Chapter 5.5 said that parameter names in a declaration are ignored entirely and did not say why either; and Chapter 4.1 introduced braces as a way of grouping statements while explicitly setting aside what else they do. All three gaps have the same answer. C has a precise rule for deciding which declaration a given use of a name refers to, the rule is worth knowing exactly rather than approximately, and it is one of the few places where a pair of braces is doing something more interesting than holding statements together.

Foundation17 min readCPart IFundamentalsFunctions

Part I · Module 5 · Chapter 5.6 · Scope: Where a Name Is Visible

1. The Question

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    printf("%d\n", count);

Which object does count refer to?

If there is exactly one declaration of count in the whole program, the question is uninteresting. Real programs are not like that. main may have a count, a function it calls may have a parameter called count, and a third function may have its own. They are different objects, and this line refers to exactly one of them.

Scope is C's answer:

The scope of a declaration is the region of the program text in which that name can be used to refer to what it declares.

Scope is a property of the source text, decided entirely while compiling. It is not about what happens while the program runs.

2. Three Questions That Get Confused

Before the rules, it is worth separating scope from two things that travel with it and are not it. Mixing them up is the single most common source of confusion in this area, and Chapter 11.5 is devoted to pulling them apart properly.

QuestionWhat it asksOwned by
ScopeWhere in the text can this name be used?this chapter
LifetimeWhen does the object exist, and when does it stop?Chapters 9.2 and 9.3
LinkageCan other source files refer to this same thing?Chapter 11.4

An object can be out of scope and still exist. An object can be in scope at a point where it does not yet hold a usable value — Chapter 2.3's uninitialised-read bug is exactly that situation. This chapter answers only the first question, and when the other two matter it says so rather than guessing.

3. Block Scope

The commonest kind. A name declared inside a block is visible from its declaration to the closing brace of that block.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int main(void)
{                           /* the outer block begins */
    int count = 1;          /* count's scope starts HERE */
    ...
}                           /* count's scope ends here */

Two parts of that are worth stating precisely.

Scope starts at the declaration, not at the top of the block. A name is not usable above the line that declares it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    printf("%d\n", later);
    int later = 5;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: use of undeclared identifier 'later'

Parameters have block scope too — the block being the function body. That is the complete answer to Chapter 5.2's claim that parameter names are private: a parameter's name is visible inside the function and nowhere else, because that is where its block is.

Nested blocks and shadowing

A block inside a block may declare the same name again. The inner declaration shadows the outer one for the length of the inner block:

Azvya Education Pvt. Ltd.VLSI Mentor
three objects, one name
#include <stdio.h>

int main(void)
{
    int level = 1;
    printf("outer block: level is %d\n", level);

    {
        int level = 2;
        printf("  inner block: level is %d\n", level);

        {
            int level = 3;
            printf("    innermost: level is %d\n", level);
        }

        printf("  back in inner: level is %d\n", level);
    }

    printf("outer block: level is %d\n", level);

    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
outer block: level is 1
  inner block: level is 2
    innermost: level is 3
  back in inner: level is 2
outer block: level is 1

Three declarations, three objects, one name. Each printf refers to whichever declaration is innermost at that point in the text. The outer ones were never modified and never destroyed — they were simply not what the name meant in there.

Chapter 4.5's compile error is this rule as well. A control variable declared in a for header belongs to the loop, so using it after the loop is use of undeclared identifier — not because the object was removed, but because the name's scope ended.

4. Resolving a Name

Five stages. First, look in the innermost block that contains the use, considering only declarations that appear above that point. Second, if nothing is found, look in the next enclosing block, and keep moving outward one block at a time. Third, the function body is the outermost block, and this is where the function's parameters are declared. Fourth, beyond the function body, look at file scope, which covers declarations made outside every function. Fifth, if the name is found nowhere, it is undeclared and the translation fails.Which declaration does this name refer to?01THE INNERMOST BLOCK CONTAINING THE USEonly declarations above this point count02NOT THERE — THE NEXT ENCLOSING BLOCKoutward, one block at a time03THE FUNCTION BODY, THE OUTERMOST BLOCKwhere the parameters are declared04BEYOND IT — FILE SCOPEdeclarations made outside every function05NOWHERE — THE NAME IS UNDECLAREDthe translation fails
Figure 1 — how a name is resolved. The search starts in the innermost block containing the use and works outward, one enclosing block at a time, stopping at the first declaration it finds. The function body is just the outermost block, which is where parameters live; beyond it is file scope. If nothing is found anywhere, the name is undeclared and the translation fails. Because the search stops at the first match, an inner declaration hides an outer one of the same name rather than conflicting with it.

The search stops at the first match. That is the whole of shadowing: an inner declaration is not in conflict with an outer one, it simply gets found first.

5. File Scope

A declaration placed outside every function has file scope: it is visible from that point to the end of the file, which means to every function defined below it.

Azvya Education Pvt. Ltd.VLSI Mentor
a name every function below can see
#include <stdio.h>

int sample_limit = 4095;

int clamp(int code)
{
    int count = 999;

    if (code > sample_limit) {
        return sample_limit;
    }

    return code;
}

int main(void)
{
    int count = 1;

    printf("limit seen from main:  %d\n", sample_limit);
    printf("clamp(5000):           %d\n", clamp(5000));
    printf("main's count:          %d\n", count);

    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
limit seen from main:  4095
clamp(5000):           4095
main's count:          1

sample_limit is visible in both functions. The two objects named count are completely unrelated — one belongs to clamp, one to main, and neither can see the other. Nothing special was needed to keep them apart; they were never in the same scope to begin with.

The function declarations in Chapter 5.5 were file-scope declarations too, which is why putting them at the top of the file made every function below able to call them.

6. Two Scopes You Have Already Met Without Knowing

C has four kinds of scope. The two above are the ones you use constantly; the other two each explain something an earlier chapter stated and did not justify.

Function scope — labels, and only labels

A label is visible throughout the function it appears in, regardless of which block it is written in. That is why two labels with the same name in one function collide even when they are in different blocks:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    goto done;
done:
    {
done:
        return 1;
    }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: redefinition of label 'done'

A variable declared twice like that would be ordinary shadowing. A label cannot be, because labels do not have block scope — and that is also why Chapter 4.8 could say that a goto reaches any label in its function but none outside it. The scope kind is the rule behind the behaviour.

Function prototype scope — the names in a declaration

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int scale(int value, int factor);

value and factor are in scope only until the end of that declaration. They refer to nothing afterwards, which is precisely why Chapter 5.5 could say the compiler ignores them and you should write them anyway: their entire audience is the person reading the line.

Scope kindWhat it applies toExtends fromTo
Blockvariables, parametersthe declarationthe enclosing block's }
Fileanything declared outside all functionsthe declarationthe end of the file
Functionlabels onlyanywhere in the functionthe function's }
Function prototypeparameter names in a declarationthe declarationthe end of that declaration

§3's nested level example compiles without a single complaint at the warning levels this curriculum has used throughout:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cc -std=c17 -Wall -Wextra -Wpedantic shadow.c -o shadow     # silent

Add one flag:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cc -std=c17 -Wall -Wextra -Wpedantic -Wshadow shadow.c -o shadow
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning: declaration shadows a local variable [-Wshadow]
warning: declaration shadows a local variable [-Wshadow]

Shadowing is not an error and is not always wrong — a short loop body declaring its own i is harmless. It becomes a bug when you meant the outer object:

Azvya Education Pvt. Ltd.VLSI Mentor
which total does the function change?
#include <stdio.h>

int total = 10;

int shadow_demo(int total)
{
    total = total + 1;
    return total;
}

int main(void)
{
    printf("returned %d, file-scope total still %d\n", shadow_demo(5), total);

    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
returned 6, file-scope total still 10
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning: declaration shadows a variable in the global scope [-Wshadow]

Two rules are doing the work, and both are already yours. The parameter total shadows the file-scope total, so total = total + 1 inside the function refers to the parameter — this chapter's rule. And the parameter is a copy of the argument 5, so incrementing it changes nothing a caller can see — Chapter 5.3's rule. The function returned 6 and nothing else in the program moved.

8. What You Should Be Able to Say Now

  1. Scope is the region of text in which a name refers to a particular declaration.
  2. Scope, lifetime and linkage are three different questions; this chapter answers only the first.
  3. Block scope runs from the declaration — not the top of the block — to the enclosing }.
  4. Parameters have block scope, the block being the function body. That is why their names are private.
  5. A nested declaration shadows an outer one for the length of the inner block; the outer object is unaffected.
  6. Resolution searches outward from the innermost block and stops at the first match.
  7. File scope reaches every function below the declaration — which is how top-of-file function declarations work.
  8. Labels have function scope, which is why two identically named labels collide across blocks.
  9. Parameter names in a declaration have function prototype scope, which is why they are ignored.
  10. Shadowing is silent without -Wshadow — the third such flag in this curriculum.

9. Exercises

Exercise 1 — predict the output. Do not compile yet.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#include <stdio.h>

int main(void)
{
    int n = 10;
    {
        int n = 20;
        printf("%d ", n);
    }
    printf("%d ", n);
    {
        n = 30;
        printf("%d ", n);
    }
    printf("%d\n", n);
    return 0;
}

The second inner block has no declaration. Explain what difference that makes.

Exercise 2 — name the scope kind. For each, say which of the four scopes applies and where it ends.

  • int code in int clamp(int code) { ... }
  • int temp declared inside a while body
  • int limit = 50; written above main and outside it
  • retry: in the middle of a function
  • factor in int scale(int value, int factor);

Exercise 3 — find the error. Say what the compiler reports and why, then give the repair.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int main(void)
{
    sum = 0;
    int sum;
    return sum;
}

Exercise 4 — trace the shadow. Give the exact output and say which object each printf refers to.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#include <stdio.h>

int value = 1;

void f(void)
{
    printf("%d ", value);
    int value = 2;
    printf("%d ", value);
}

int main(void)
{
    f();
    printf("%d\n", value);
    return 0;
}

Exercise 5 — explain the label error. Two blocks in one function both contain done: and the compiler reports a redefinition, yet two blocks may both declare int x. Explain the difference using §6, and say which chapter's behaviour this explains.

Exercise 6 — find the silent bug. This is meant to accumulate into the file-scope readings_seen. Say what it actually does, which flag would have revealed it, and give two repairs — one that renames and one that removes the shadowing entirely.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int readings_seen = 0;

void record(int readings_seen)
{
    readings_seen = readings_seen + 1;
}

Exercise 7 — argue the design. §5's sample_limit is at file scope and works. Give two reasons from Chapter 5.1 to pass it as a parameter instead, and one case where leaving it at file scope is reasonable.

Exercise 8 — assemble the flags. Write the compiler command you would use for a serious project, incorporating the three flags named in §7, and give a one-line justification for each.

10. Summary

  • Scope is where in the source a name refers to a given declaration — a compile-time, text-based property.
  • It is not lifetime and not linkage; Chapters 9.2, 9.3 and 11.4 own those, and Chapter 11.5 separates all three.
  • Block scope begins at the declaration and ends at the enclosing brace. Parameters have it, over the function body.
  • Resolution goes innermost outward and stops at the first match — which is all shadowing is.
  • File scope covers everything below the declaration, including function declarations.
  • Labels have function scope; parameter names in a declaration have function prototype scope.
  • Shadowing is legal and silent — -Wshadow is the flag, and it is the third such flag the curriculum has needed.
  • Prefer passing what a function needs over reaching for a file-scope object.

Scope answers where a name may be used, and it was careful to say nothing about when the object exists. Those really are different questions, and the difference becomes concrete the moment you ask where the three objects named level actually were while the program ran, or why a parameter stops existing when a function returns, or what happens to all of that when one function calls another that calls another. The next chapter builds the picture every real implementation uses for exactly this — and is equally careful about which parts of it are the machine's arrangement rather than the language's rule.

Standards & specifications

Governing standard
ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)

Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C Programming curriculum.