The static Keyword

This article discusses the static keyword, its use on global variables, class members, global functions and class functions. There is also a discussion about the use of namespaces in C++, and also how an un-named namespace is usually a preferable thing as compared to using the static keyword for internal linkage.

Last Reviewed and Updated on February 7, 2020
Posted by Parveen(Hoven),
Aptitude Trainer and Software Developer

My C/C++ Videos on Youtube

Here is the complete playlist for video lectures and tutorials for the absolute beginners. The language has been kept simple so that anybody can easily understand them. I have avoided complex jargon in these videos.


Variables outside any functions and blocks are called global variables. For example. if you define an int type of variable just above your main function, then it is a global variable. A variable created inside main function, or inside other functions, or inside plain blocks within functions are not global variables; they are called local variables.

Local variables are not accessible outside the block in which they have been created. They die the moment the program control moves past the last closing brace of their container. That's why the compiler doesn't allow you to read or write to that variable from outside the containing scope. You can run this code to see that the object objA is deleted just before the enclosing block is exit.

class CMyClass
{
    int mID;
 
public:
    CMyClass(int ID) : mID(ID)
    {
        cout << "Created ID = " << mID << endl;
    }
 
    ~CMyClass()
    {
        cout << "Deleted ID = " << mID << endl;
    }
public:
    void fx()
    {
        cout << "fx called!" << endl;
    }
};
 

int main()
{    
    cout << "Before block" << endl;
    
    {
        // object created here
        CMyClass objA(2);
 
        // destroyed at tthis point
    }
 
    cout << "After block" << endl;
    
    // ERROR: objA is dead long ago
    // objA.fx();
    return 0;
}

// output of the above code is:
Before block
Created ID = 2
Deleted ID = 2
After block

Global variables are created when a program starts. They are the first to become live. They are created even before the main function is entered. The order of creation is usually determined by the order in which they are initialized inside a file. But it is a very complicated subject. Neither is the order of creation, nor the order of destruction are very easy to predict for variables inside a file and for those competing from other files of your project. For now, one thing is guaranteed that all global variables are created, and up and alive before the main begins its execution. And, all the global variables are cleared away only after the main has exited.

We can now have a look at the following program. The class below has a data member, mID, that will help us know which object is being created and which one is being destroyed. Two objects of the class are created - one before main, and the other after the main. Both of them are global objects created at two different points in the same file. Their constructors are called before main is entered, and destructors called after main.

class CMyClass
{
    int mID;
 
public:
    CMyClass(int ID) : mID(ID)
    {
        cout << "Created ID = " << mID << endl;
    }
 
    ~CMyClass()
    {
        cout << "Deleted ID = " << mID << endl;
    }
};
 
CMyClass objA(0);
 
int main()
{    
    cout << "main starts execution" << endl;

 
    cout << "main ends execution" << endl;
    
    return 0;
}
 
CMyClass objC(2);

// the output of the above code is:
Created ID = 0
Created ID = 2
main starts execution
main ends execution
Deleted ID = 2
Deleted ID = 0

Global Variables are static in Nature

Global variables are static - they stay the longest; once created, they continue to stay. They are static. The static word that I have used here is in the sense of English language. It is a pure coincidence that there is a keyword with the same spellings in the C and C++ languages. Beginners face a lot of problem with the usage, context and meanings of the word static, and the keyword static. Both are entirely different. When static is used in the context of English language, then it has only one meaning - something stable, durable, firmly fixed, and having a long life, like God in relation to his Universe. But when the same static is used as a keyword, then it has numerous meanings, and implications, depending on where it is used, and on what it is applied to. Let's see them one by one.

static Keyword on Global Variables and Functions

The static keyword against a global variable has no effect on the lifetime of that variable. This is the first thing. The variable will still be created before the main, and will live past it. It will be global for all purposes except that it becomes local to the file in which it has been created. It cannot be accessed outside its file - not even with the "extern" keyword. This means that static variables with the same name can concurrently exist in different translation units of a project. They are not cross-file-discoverable to the linker. No linker errors are thrown if you have ten static int i living in 10 different cpp files of a project.

Local functions are not possible in C++. It is not possible to define a named function within another function [Workarounds are possible through lambda expressions and closures. Another workaround is by defining a struct within a function and then putting a static function inside it. But that's a different story.]. Functions have addresses so they occupy memory, and all your global functions are allocated memory at startup, as if they were "read only" global variables. They are available across translation units. The linker can discover them when it is linking. But when the static keyword is applied to a global function it affects its visibility, in the same way as it does for a global variable. A static function is not visible outside its file. static global functions having the same signature can co-exist in the various translation units of a project.

In summary, the static keyword affects only the visibility when it is applied to global functions and variables.

static Keyword on Member Variables

The static keyword can be applied to data members of a class or struct [but not to data members and functions of a union]. When applied to data members, it causes the member to be shared by all objects of that class or struct. A non-static data member is a part of the object. So if you have a class with an int i, and two objects of that class are created, then each object has its own i. The data member is not shared by the objects. When a member function is called on that object, it acts on the non-static data members of that object. This can be seen by examining the following code.

struct SMyStruct
{
    int i;
 
    void fx()
    {
        cout << i << endl;
    }
};
 
int main()
{    
    SMyStruct objA, objB;
 
    objA.i = 99;
 
    objB.i = 67;
 
    // prints 99, the value of
    // i contained inside objA
    objA.fx();
 
    // prints 67, the value of
    // i contained inside objB
    objB.fx();
 
    return 0;
}

When a data member is marked static, then it becomes a shared member. It is not bound to a particular object.

The initialization of a static data member is not possible through the constructor, because the code inside a constructor runs each time a new object is created. Suppose, for the sake of argument, that it were possible, and you give some initial value through a constructor, and subsequently, that data member is altered, say, through another function call. Then as soon as another new object is created, the value would reset back each time - this makes the whole show unpredictable. The static members are common, and shared. They are attached to the containing class or struct, not its instances.

The static data members of a class are more like static global variables constrained or scoped to a file, except that they[class statics] are scoped to the containing class. This means that it is possible to have as many variables of the same name, and within the same file, provided they are static data members of different classes within that file. This opens up wider possibilities at preventing name clashes.

The initialization of global variables can occur in two ways. First is the automatic initialization - done by the compiler, and the other is the explicit initialization - done by the programmer. If a global variable is declared, but no value is assigned at that time, the compiler gives it an initial value such as 0, 0.0f or 0.0, or by calling its default constructor, all depending on the type of the variable. This is implicitly done by the compiler. A global variable, whether static or not, never contains a garbage value.

The same rules apply to static data members of a class also. They can be left un-initialized, or can be given any initial value by the developer. The compiler doesn't allow them to be initialized inside the class, at the point of declaration, like so below.

struct SMyStruct
{
    // COMPILER ERROR
    static int i = 40;
 
    void fx()
    {
        cout << i << endl;
    }
};

They must be specifically defined outside the class so that the linker can find them along with the other global variables. All global variables whether the statics of the class or the static/non-statics of a file must be defined in the "open space" of a translation unit, the space which is outside of all functions, classes, structs, unions and namespaces. If you don't define your static data members, and attempt to use them, the linker will throw an error telling you that it couldn't find that static. This is how a static should be defined.

struct SMyStruct
{
    static int i;
 
    void fx()
    {
        cout << i << endl;
    }
};
 
// initialization of the shared i
int SMyStruct::i = 80;
 
int main()
{    
    return 0;
}

Notice that the full path of a static variable is required to be given. Notice also that the static keyword is not repeated at the time of definition. The statics of a class are always global. They can be accessed from any translation unit of your project - they cannot be restricted to a file - that's why the compiler won't allow them to be marked static when defining them.

static Functions Inside Classes

We have seen that when static keyword is applied to data members of a class, they become shared members. We would expect static functions, too, to become shared functions. But that doesn't make any sense because functions of a class are already shared. As an optimization, the compiler doesn't make copies of functions when creating the class objects. It's simply not practical. The compiler writes(allocates) the code at a common place, and all the class objects access the code from that place. The functions of a class are already shared, so this can't be the purpose of writing static functions inside classes. The other possibility of restricting the visibility of class functions to a file is also ruled out because neither the class static data members, nor the functions are allowed to be scoped to a file - they have to be accessible anywhere from a project, wherever that class can be seen, its objects be created and used.

What, then, happens when a class function is marked static?

Let's begin exploring, things are not difficult. We know that the functions of a class are shared already. Consider a simple class that has a data member and a function as shown below.

struct SMyStruct
{
    int i;
 
    void fx()
    {
        cout << i << endl;
    }
};
 
int main()
{    
    SMyStruct objA, objB;
 
    objA.i = 99;
 
    objB.i = 67;
 
    // prints 99, the value of
    // i contained inside objA
    objA.fx();
 
    // prints 67, the value of
    // i contained inside objB
    objB.fx();
 
    return 0;
}

If the function fx is shared, and it has just that single statement inside it, then how does it know: who's i it has to act upon? What is this statement doing: objA.fx()? One thing is sure that the dot operator is doing some magic here. Don't forget that at the microprocessor level everything translates into subroutines and one liner machine statements. The microprocessor doesn't know about classes and objects. It knows only how to store numbers in registers, and RAM locations, and call and jump, to and from subroutines. When we write classes and objects, they are something between us and the compiler. They are an aid to writing an organized and maintainable code. Our objects and functions are all grinded into machine language beyond that. So, a C++ statement like objA.fx() is first translated to a function call something like this: fx(&objA). And, all the function definitions of a class are translated to pure global functions with signatures like: fx(SMyStruct* this, ...arguments). For example, if a class CA has function called gx(int i, char c), the function is actually translated to a global function gx(CA* this, int i, char c). An additional argument is added to the function, and its identifier is called "this". And, all function calls like objA.fx() are translated to simple subroutine calls like: fx(&objA), or gx(&CA, 6, 'B'), etc., The "this" pointer is a hidden expression that contains the address of the current object. This address is then used to reach the data member of that object. [Please take note that what I have explained just now is a schematic of how a compiler could implement things. There is nothing in the C++ Standard that lays down that things should be done in this way only.] The code below shows how the compiler allows us to use "this" pointer inside a non-static function. Compile and run to see for yourself:

struct SMyStruct
{
    int i;
 
    void fx()
    {
        cout << this->i << endl;
    }
};
 
int main()
{    
    SMyStruct objA, objB;
 
    objA.i = 99;
 
    objB.i = 67;
 
    // prints 99, the value of
    // i contained inside objA
    objA.fx();
 
    // prints 67, the value of
    // i contained inside objB
    objB.fx();
 
    return 0;
}

When the static keyword is applied to a member function, the compiler doesn't pass "this" pointer to it. It's signature is not "changed" to accommodate that additional argument. The static function uses the class boundaries for scoping itself, but is otherwise not connected to the other non-static members of that class. So, a static function cannot access non-static data members and functions because there is no "this" pointer that can help it reach that data. Secondly, such functions can access other static data members of that class because the latter are not attached to any specific object. Static functions of a class can access other static functions of that class because they would be in turn accessing only the statics of that class. Thirdly, static functions are marginally faster because there is a saving in machine cycles that would be required to pass the "this" argument.

Accessing static Members of a Class

The static members of a class can be accessed with the help of the scope resolution operator.

struct SMyStruct
{
    static int i;
 
    static void fx()
    {
        cout << i << endl;
    }
};
 
int SMyStruct::i = 9;
 
int main()
{    
    // call a static function
    SMyStruct::fx ();
 
    // access a static member
    SMyStruct::i = 80;
 
    SMyStruct::fx ();
 
    SMyStruct obj;
 
    // static members can be
    // accessed through a 
    // class object also
    obj.i = 60;
 
    obj.fx();
 
    return 0;
}

static and const

Both the keywords can be applied together on a global variable to promise that it won't be changed, and to restrict its visibility to the containing file. In the following code, the variable i will get internal linkage[i.e., it would be visible only within this file] and will also become a constant. The use of the const keyword allows the compiler to write an optimized code because it can substitute that constant value everywhere it occurs, and thus avoid allocating memory for it. This is same as the #define preprocessor directive that causes blind substitutions, without taking care of the types involved. The const keyword, on the other hand, achieves the same objective but with the added advantage that the data types are taken care of. It ensures that unnecessary bugs don't creep in.

const static int x = 9;
 
int main()
{    
    cout << x << endl;
 
    return 0;
}

The same keywords can be applied against a data member of a class. They have the same effect - promise that the variable won't be changed, it's scope restricted to the containing class, and that the variable would be shared by all the objects. C++ allows integral constants[int, short, char, long, long long] to be given values inside the class definition itself.

class CMyClass
{
public:
    static const long long k = 88LL;
};
 
int main()
{    
    cout << CMyClass::k << endl;
 
    return 0;
}

For everything else the definition must be given outside the class, at the file scope. The code below shows how the constant PI can be put in one place and then used throughout your project, not only by objects of the same class, but also by other classes and functions. Software developers use this feature to put all their constants in one file, and then use them elsewhere in their current object.

class CMyClass
{
public:
    static const float PI;
};
 
const float CMyClass::PI = 3.14f;
 
int main()
{    
    cout << CMyClass::PI << endl;
    return 0;
}

A class can contain objects of other classes as static const members. They also have to be initialized outside the class in the same manner. This is an example.

class CA
{
public:
    CA(int)
    {
    }
};
 
class CMyClass
{
public:
    static const CA mca;
};
 
const CA CMyClass::mca = CA(9);
 
int main()
{    
    CMyClass obj;
 
    return 0;
}

C++ doesn't allow mixing of static and mutable keywords. The following code gives a compiler error.

class CMyClass
{
public:
 
    // COMPILER ERROR
    static mutable CA mca;
};

static Variables inside Functions

Functions can contain static variables. These statics are also allocated in the global area of memory. Their life starts when they are first accessed, and life ends when the program ends. The initialization of static variables occurs only once - the first time the control passes through the initialization statement. In the code below, the value of i is set to zero when the function is called the first time. For any subsequent calls, the initialization statement is skipped. And since, i is a static, persistent, it retains its value across function calls. So each time the function is called, the value if i is incremented by one. This code can be used to know the number of times your function was called.

void fx()
{
    static int i = 0;
 
    cout << i << endl;
}
 
int main()
{    
    fx();
 
    fx();
 
    return 0;
}

A function can contain static const variables also. Both these keywords cause the allocation to be in the global area of memory, and at the same time prevent changes to the variable. Such variables are likely to be folded away by an optimizing compiler - it would remove them altogether by making value substitutions, if that is possible.

Not only can variables be static, and not only can they be static const, they could be just const as well. Consider this code.

void fx()
{
    static const int i = 0;
 
    const int j = 99;
 
    cout << i << endl;
 
    cout << j << endl;
}

In the above code j is const, whereas i is static const. I am absolutely sure that both of them will be folded by a compiler. So what's the difference? When it comes to literals, the difference is not going to be much, except when your code doesn't apply the address of operator on a variable. When you evaluate the address of a variable, the compiler is forced to make an allocation. So if that is the case, then i goes to the global memory area, and it will persist. And, j will be pushed and popped off the stack each time the function is called.

Consider the following code where we have a static const object of a class. You can run this code to see that the constructor is called only once.

class CA
{
public:
    CA()
    {
        cout << "ctor called" << endl;
    }
};
 
void fx()
{
    static const CA obj;
}
 
int main()
{    
    fx();
 
    fx();
 
    return 0;
}

If we remove "static" in the above code, then we can see that each time the function fx is called, the constructor[and, therefore, the destructor] will run as well. So, small things can inadvertently lead to huge consequences in terms of performance.

class CA
{
public:
    CA()
    {
        cout << "ctor called" << endl;
    }
};
 
void fx()
{
    const CA obj;
}
 
int main()
{    
    fx();
 
    fx();
 
    return 0;
}

statics inside a Namespace

Variables created inside a namespace are global, and they are scoped to the namespace name. To access them from outside the namespace, the scope resolution operator must be used. A C++ file can contain many namespace blocks having the same name, all within the same file. When a namespace block having the same name is present in different translation units, it is called spanning. So, a namespace can span multiple files. If you create a non-static variable in a namespace in one file, and another non-static variable with the same name and within the same namespace inside another file or inside the same file, a linker error occurs, similar to the one that would occur had the two variables been created in the global namespace. We can say, that the global free space is a sort of a namespace that can be further carved into more namespaces, and variables inside a namespace are entirely shielded from those in others. If you write a static variable inside a namespace, then this variable is shielded from variables of the same name and in the same namespace appearing in other translation units of your project. The use of static keyword against a variable in a named namespace is to scope it to a single file, and within that file to that namespace. Here is an example that shows four variables with the same co-existing in the same file.

namespace ns
{
    int i = 6;
}
 
namespace ns2
{
    int i = 62;
}
 
namespace
{
    int i = 7;
}
 
int main()
{
    int i = 5;
 
    // local 
    cout << i << endl;
 
    // namespace ns
    cout << ns::i << endl;
 
    // namespace ns2
    cout << ns2::i << endl;
 
    // unnamed namespace
    cout << ::i << endl;
 
    return 0;
}

Namespaces without any name are called un-named namespaces. One variable above is inside an un-named namespace. Variables inside an un-named namespace are scoped to the current file only - as if static keyword were used against a global. If possible, avoid the use of static keyword for giving internal linkage to a variable; put in an un-named namespace instead. The C++ standard once deprecated the use of static keyword for giving internal linkage. I personally think that using an un-named namespace is certainly a better option from the code readability point of view.



Creative Commons License
This Blog Post/Article "The static Keyword" by Parveen (Hoven) is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Updated on 2020-02-07. Published on: 2015-11-29