1. Homepage of Dr. Zoltán Porkoláb
    1. Home
    2. Archive
  2. Teaching
    1. Timetable
    2. Bolyai College
    3. C++ (for mathematicians)
    4. Imperative programming (BSc)
    5. Multiparadigm programming (MSc)
    6. Programming (MSc Aut. Sys.)
    7. Programming languages (PhD)
    8. Software technology lab
    9. Theses proposals (BSc and MSc)
  3. Research
    1. Sustrainability
    2. CodeChecker
    3. CodeCompass
    4. Templight
    5. Projects
    6. Conferences
    7. Publications
    8. PhD students
  4. Affiliations
    1. Dept. of Programming Languages and Compilers
    2. Ericsson Hungary Ltd

7. Scope

Scope and Life rules

In imperative programming languages variables have two important properties:

  1. Scope - the AREA in the program where a name is binded to a memory area.

  2. Life - the TIME under run-time when the memory area is valid and usable.

Life and scope is defined by the declaration of the variables. More precisely, the place of the declaration and the specified storage class is important. Declaration also specifies the type of the language objects.

Scope rules

Scope rules define the section of the source where a name is binded to some C++ objects (e.g. variable, typename, enum, etc.).

For an overall view we will consider the following source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// file1.cpp
#include <iostream>

int i;          // global, with external linkage
static int j;   // global, without external linkage
extern int n;   // global, defined somewhere else

namespace X     // namespace X
{
    int i;      // X::i

    int f() // X::f()
    {
        i = n;      // X::i = ::n; 
        return j;   // ::j
    }
    void g(); // X::g()
}

namespace       // anonymous namespace
{
    int k;  // ::k
}

void f()
{
    int i;          // local i
    static int k;   // local k  
    {
        int j = i;  // local j, (i is the one declared above)
        int i = j;  // local i

        std::cout << i << j << k << X::i << ::k << ::i;
    }
}

static void g()
{
    ++k;    // ::k
}

void X::g() // X::g()
{
    ++i;    // X::i
}

Globals

Global names (variables, functionas, types/classes) are defined outside of any block.

1
2
3
int i;          // global, with external linkage, definition
static int j;   // global, without external linkage, definition
extern int n;   // global, defined somewhere else, declaration

Global variables has external linkage by default; i.e. they are visible from other source files (after declaration) via the linker. They provides a communication possibility between source files. In the other source file(s) they should be declared using the extern keyword. Global variables should be defined in exactly one source file: here will be the variable allocated.

Global variables defined with static keyword are defined in the source file without externak linkage, e.g. they are invisible from other translation units (source files). Global static functions can be called from the same translation unit but invisible from outside.

Using anonymous namespace is suggested instead of glocal static definitions.

Namespaces

Namespaces provide name-level (but not file-level) separations of modules. We can group the brach of types/classes, functionas and variables sharing some common property or belonging to the same logical module in one semantical unit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace X     // namespace X
{
    int i;      // X::i

    int f() // X::f()
    {
        i = n;      // X::i = ::n; 
        return j;   // ::j
    }
    void g(); // X::g()
}

void X::g() // X::g()
{
    ++i;    // X::i
}

Namespace names are visible from the namespace itself, or from the internal block of the functions belonging to the correspontong namespace (like X::i). From outside of the namespace the scope operator (::) is used to qualify the identifiers of the namespace.

To shorten the superflous scope operators, one can use the use declarations make a namespace name visible inside a block. Multiple names can be declared by using namespace.

Namespaces can be nested. Namespaced are open: one can extend a namespace just add new elements to an existing namespace.

Classes form a special namespace with the class name.

Anonymous namespace

Names unique to the current translation unit can be declared in a special namespace without name.

1
2
3
4
5
6
7
8
namespace       // anonymous namespace
{
    int k;  // ::k
}
static void g()
{
    ++k;    // ::k
}

Linkage of names defined in the anonymous namespace will be unique, therefore these names can be used only inside the current translation unit. As anonymous namespace is considered automatically “using namespace”-d, these names can be use without scope operator.

Local names

Locals are defined inside a (non-namespace) block.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void f()
{
    int i;          // local i
    static int k;   // local k  
    {
        int j = i;  // local j, (i is the one declared above)
        int i = j;  // local i

        std::cout << i;     // local i declared in this block
        std::cout << j;     // local j declared in this block
        std::cout << k;     // local k declared in outer block
        std::cout << X::i;  // i from namespace X 
        std::cout << ::k;   // k from anonymous namespace
        std::cout << ::i;   // global i
    }
}

A local definition or declaration hides the same name declared outside of the block. Internal blocks can be used to define/declare other similar names and may lead other hidings.

Objects with hidden names (when their memory is valid) still can be accessed by pointers or references or other ways, when their names are not visible.

Namespace and global variables when hidden can be accessed via the scope operator.

There are no local functions in C++, functions should be defined as global or namespace elements. However, one can define lambda functions as local objects since C++11.

Hints

Too wide scope can be the root of errors.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int i;  // dangerous
int main()
{
    if ( ... )
    {
        int j = 1;
        //...
        ++i;    // ++j instead of j
    }
    int j = 0;
    while ( j < 10 )
    {
        //...
        i++; // instead of j++
    }
    for (i = 0; i < 10; ++i)
    {
        //...
    }
    ++i;    // global i   
}

Minimizing the scope is always a good idea!

1
2
3
4
5
6
7
int main()
{
    for ( int i = 0; i < 10; ++i )
    {
        // i is local here
    }
}
Financed from the financial support ELTE won from the Higher Education Restructuring Fund of the Hungarian Government.