Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

static Declarations, Part I

Last updated Jan 1, 2003.

The static keyword is context dependent. It may indicate not less than four different concepts: internal linkageof namespace-scope identifiers, local variables that remain in existence throughout the program's execution, static data members of a class, or static member functions. In this installment, I discuss the semantics of static declarations of the C subset of C++, namely local statics and namespace scope statics.

NOTE

The following sections describe the semantics of static objects of POD types exclusively. The rules of static objects of non-POD types are different, and will be discussed in the second part of this article. The terms variables and objects are used interchangeably in this section, as are function and block.

Local statics

static variables declared inside a block are local to that block, similarly to auto variables. The following function uses the local static variable calls to track the number of its invocations:

int count_calls()
{
 static int calls=0; //local static
 return ++calls;
}

Unlike auto variables, local statics aren't destroyed upon leaving their function; they remain in existence until the program terminates. As such, they provide private and permanent storage for a function. When that function is invoked again, it will access the same calls variable.

How does this feat work? static objects aren't allocated on the stack, but on a permanent storage section that remains active throughout the program's execution. Implementations usually allocate a special block in each .exe file for objects that remain in existence as long as the program is running. Namespace-scope objects (e.g. globals) and local statics are stored in this block. If you have the address of calls, you can manipulate it even when the name calls is not in scope:

int count_calls(int *& rpi)
{
static int calls=0;
rpi=&calls;
return ++calls;
}
int main()
{
int *pi;
int n=count_calls(pi); 
cout <<n<<endl; //output 1
*pi=5; //modify calls, although it's not in scope
n=count_calls(pi); 
cout <<n<<endl; //output 6
}

This property of static objects can lead to race conditions in multithreaded apps, e.g. when one thread is assigning a new value to a static variable while another thread is reading its value. The solution is to use synchronization objects that selectively grant access to static variables.

Initialization

As opposed to auto and dynamic variables, static variables - either local or external - are zero-initialized by default, unless their declaration contains an explicit initializer with a different value:

int count_calls(int *& rpi)
{
static int calls=1; //non-zero initialization
return calls++; //postfix 
}

The explicit initializer of calls in this case:

int count_calls(int *& rpi)
{
static int calls=0;
...

is therefore redundant. However, it's still a good idea to use an explicit initializer in every static declaration to document your intention explicitly.

Local static variables are conceptually initialized before their block is first entered. Aggregates, i.e. POD structs, POD unions and arrays of POD elements are initialized to binary zeroes, unless the declaration contains an explicit initializer with a different value:

struct Date 
{
 int d;
 int m;
 int y;
};
void func()
{
 static Date d; //zero-initialized by default
 static int num[10]; //all elements are zero-initialized
 static Date Xmas={25,12,2004};
}

Namespace Scope static Declarations

When applied to a namespace scope object or function, static restricts their scope to the rest of the translation unit, thereby enabling a programmer to implement the information hiding principle even in a procedural language such as C:

//inaccessible from other translation units
static void decrypt(char *msg) 
{
//...
}

If decrypt() is to be used solely by one function in the program, move that function into the same translation unit of decrypt(). Similarly, you can restrict access to namespace scope objects by declaring them static:

static char CRC[5]; //internal linkage
static Date d; //ditto
int main()
{
//..
}

This technique can be used for reducing the risk of name conflicts across translation units and for implementing the information hiding principle.

NOTE

In standard C++, this type of static declarations is deprecated. In normative C++ programs you should use an unnamed namespace instead:

namespace //unnamed
{
 char CRC[5];
 Date d; 
}

Although the underlying machinery differs, the effect of declaring objects and functions in an unnamed namespace is the same -- they can only be accessed from within their translation unit.

Readers have asked me several times why the C++ standards committee frowned at namespace scope static declarations. The first problem was didactic. The keyword static was already overloaded in C; C++ added to this confusion the concept of static data members and static member functions. The committee felt that static was becoming a Swiss army knife: a single keyword that does too many different things.

Another problem was more subtle. Not all implementations truly support internal linkage. Therefore, the "internal-linkage" promise couldn't be fulfilled in certain environments; hackers familiar with the inner-workings of such implementations could access allegedly-invisible functions and data declared in other translation units. An unnamed namespace, of course, doesn't cure broken linkers; however, it is more honest about its capabilities.

Article 7.3.1.1/1 of the C++ standard says: "[a]lthough entities in an unnamed namespace might have external linkage, they are effectively qualified by a name unique to their translation unit and therefore can never be seen from any other translation unit." Put differently, an unnamed namespace restricts the visibility of its members to the scope of the translation unit by means of name mangling; it doesn't necessarily guarantee internal linkage, though.

Do these arguments sound convincing? In the next installment I will show that standard C++ itself forces programmers to violate this normative recommendation in one case. Additionally, I will discuss the initialization phases of non-POD objects with static storage type, as well as static class members.