- Overview
-
Table of Contents
- Special Member Functions: Constructors, Destructors, and the Assignment Operator
- Operator Overloading
- Memory Management
- Templates
- Namespaces
- Time and Date Library
- Streams
- Object-Oriented Programming and Design Principles
- The Standard Template Library (STL) and Generic Programming
- Exception Handling
- Runtime Type Information (RTTI)
- Signal Processing
- Creating Persistent Objects
- Bit Fields
- New Cast Operators
- Environment Variables
- Variadic Functions
- Pointers to Functions
- Function Objects
- Pointers to Members
- Lock Files
- Design Patterns
- Dynamic Linking
-
Tips and Techniques
- Using the Swap() Algorithm
- Using class stopwatch for Performance Measurements
- Extending <tt><iostream></tt> to Support User-Defined Types
- Using <tt>auto_ptr</tt> To Automate Memory Management
- Using <tt>auto_ptr</tt> To Automate Memory Management, Part II
- Using <tt>auto_ptr</tt> To Automate Memory Management, Part III
- Using <tt>enum</tt>s as Mnemonic Indexes
- Create Objects on Pre-Allocated Memory Using <tt>Placement-new</tt>
- Online Books: <tt>Placement-new</tt>
- Bitwise Operators
- Bitwise Operators II
- Who's <tt>this</tt>?
- A Reference Guide
- The Virtues of Multiple Inheritance
- Interfaces
- Multiple Inheritance: Construction and Destruction Order
- nothrow new
- POD Initialization
- Object Initialization
- <tt>const</tt> Declarations
- The Semantics of <tt>volatile</tt>
- <tt>inline</tt> Functions
- Project Organization Guidelines
- All About <tt>bool</tt>
- <tt>typedef</tt> Declarations
- State of the <tt>union</tt>
- Dynamic Cast Uses
- Integrating C and C++
- <tt>const</tt>-Correctness
- <tt>const</tt>-Correctness: Advanced Issues
- Sprucing Up Legacy Code
- Virtual Constructors
- Naming Names
- Function Calls
- Speaking Standardese (updated)
- Speaking Standardese: the One Definition Rule
- Declarations and Definitions
- More on Declarations and Definitions
- The Most Vexing Parse
- Finally, At Last
- Sound Bytes (Admittedly Off Topic)
- Local Classes
- Complex Arithmetic
- Floating Point Woes
- String Manipulation
- The Object Model
- The Object Model II
- The Object Model III
- Temporary Objects
- Temporary Objects: Advanced Techniques
- Over-Engineering
- Security Enhancements
- Drop the (automatic) Pilot
- Choosing the Right Container
- Choosing the Right Container II
- Choosing the Right Container, Part III
- Arrays and Pointers
- Low-Level File I/O
- Low-Level File I/O Part II
- static Declarations, Part I
- static Declarations, Part II
- <code>static</code> Initialization Order
- Revisiting the Deprecation of File-Scope Static
- Virtual Memory and Memory Mapping
- Cellular Phone Programming Guidelines
- The Handle/Body Idiom
- Whole Program Optimization, Part I
- Whole Program Optimization, Part II
- Manipulating Directories
- Window Dressing
- <code>friend</code> Declarations
- <code>friend</code> Part II: the Interaction of Friendship and Template Classes
- Forcing Object Allocation on Specific Storage Types
- Lazy Evaluation
- Cache and Carry
- Controlling a Container’s Capacity
- Non-Blocking I/O, Part I
- Non-Blocking I/O, Part II
- Using Unions for Automatic Conversion
- Launching a Child Process
- <tt>switch</tt> Statements
- Introducing the "struct Hack"
- Scoped Enumerators
- Doing Statistics with STL
- Fixing the "Unresolved External" Linkage Error
- Understanding Calling Conventions
- Understanding the Empty Base Optimization
- Implementing RPC with the door Library, Part 1
- Implementing RPC with the door Library, Part 2
- Eliminating Two Common Pointer and <tt>sizeof</tt> Bugs
- Command Line Arguments
- Performance Myths Busting
- Tag Names And Types Part I
- Tag Names And Types Part II
- The Infamous goto
- Trimming Strings
- Can Objects Live Forever? Part I
- Can Objects Live Forever? Part II
- Five Ways to Improve Your Functions
- Member Aggregate Initialization
- Five Futile Coding-Style Debates
- The Good Parasite Idiom: An Exercise in OOD
- The Good Parasite Idiom: An Exercise in OOD, Part II
- The Good Parasite Idiom: An Exercise in OOD, Part III
- Ten Techniques to Reduce the Size of Your Classes, Part I
- Ten Techniques to Reduce the Size of Your Classes, Part II: Inheritance Issues
- Ten Techniques to Reduce the Size of Your Classes, Part III
- Ten Techniques to Reduce the Size of Your Classes IV
- Taking the Address of an Object with an Overloaded Operator <tt>&</tt>
- strcpy() -- How and Why Does It "Just Work"?
- Anonymous Structs
- Five Easy Ways to Reduce The Size of your Executables
- Standard Layout Classes and Trivially Copyable Types, Part I
- Standard Layout Classes and Trivially Copyable Types, Part II
- Five Simple Code Sanity Checks
- Five Things You Need to Know About C++11 Unions
- A Tour of C99
- A Tour of C1X
- C++0X: The New Face of Standard C++
- C++0x Concurrency
- The Reflecting Circle
- We Have Mail
- The Soapbox
- Numeric Types and Arithmetic
- Careers
- Locales and Internationalization
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.
