Inapoi
Inainte
Cuprins
Static initialization
dependency
Within a specific translation unit, the
order of initialization of static objects is guaranteed to be the order in which
the object definitions appear in that translation unit.
The order of destruction is guaranteed to be the reverse of the order of
initialization.
However, there is no guarantee concerning
the order of initialization of static objects across translation units,
and the language provides no way to specify this order. This can cause
significant problems. As an example of an instant disaster (which will halt
primitive operating systems and kill the process on sophisticated ones), if one
file contains
// First file
#include <fstream>
std::ofstream out("out.txt");
and another file uses the out
object in one of its initializers
// Second file
#include <fstream>
extern std::ofstream out;
class Oof {
public:
Oof() { std::out << "ouch"; }
} oof;
the program may work, and it may not. If
the programming environment builds the program so that the first file is
initialized before the second file, then there will be no problem. However, if
the second file is initialized before the first, the constructor for Oof
relies upon the existence of out, which hasn’t been constructed yet
and this causes chaos.
This problem only occurs with static
object initializers that depend on each other. The statics in a
translation unit are initialized before the first invocation of a function in
that unit – but it could be after main( ). You can’t be
sure about the order of initialization of static objects if they’re in
different files.
A subtler example can be found in the
ARM.[47]
In one file you have at the global scope:
extern int y;
int x = y + 1;
and in a second file you have at the
global scope:
extern int x;
int y = x + 1;
For all static objects, the
linking-loading mechanism guarantees a static initialization to
zero before the dynamic
initialization specified by the programmer takes place. In the previous example,
zeroing of the storage occupied by the fstream out object has no special
meaning, so it is truly undefined until the constructor is called. However, with
built-in types, initialization to zero does have meaning, and if the
files are initialized in the order they are shown above, y begins as
statically initialized to zero, so x becomes one, and y is
dynamically initialized to two. However, if the files are initialized in the
opposite order, x is statically initialized to zero, y is
dynamically initialized to one, and x then becomes two.
Programmers must be aware of this because
they can create a program with static initialization dependencies and get it
working on one platform, but move it to another compiling environment where it
suddenly, mysteriously, doesn’t
work.
What to do
There are three approaches to dealing
with this problem:
- Don’t do it.
Avoiding static initialization dependencies is the best
solution.
- If you
must do it, put the critical static object definitions in a single file, so you
can portably control their initialization by putting them in the correct
order.
- If
you’re convinced it’s unavoidable to scatter static objects across
translation units – as in the case of a library, where you can’t
control the programmer who uses it – there are two programmatic techniques
to solve the problem.
Technique one
This technique was pioneered by Jerry
Schwarz while creating the iostream library (because the
definitions for cin, cout, and cerr are static and
live in a separate file). It’s actually inferior to the second technique
but it’s been around a long time and so you may come across code that uses
it; thus it’s important that you understand how it works.
This technique requires an additional
class in your library header file. This class is responsible for the dynamic
initialization of your library’s static objects. Here is a simple
example:
//: C10:Initializer.h
// Static initialization technique
#ifndef INITIALIZER_H
#define INITIALIZER_H
#include <iostream>
extern int x; // Declarations, not definitions
extern int y;
class Initializer {
static int initCount;
public:
Initializer() {
std::cout << "Initializer()" << std::endl;
// Initialize first time only
if(initCount++ == 0) {
std::cout << "performing initialization"
<< std::endl;
x = 100;
y = 200;
}
}
~Initializer() {
std::cout << "~Initializer()" << std::endl;
// Clean up last time only
if(--initCount == 0) {
std::cout << "performing cleanup"
<< std::endl;
// Any necessary cleanup here
}
}
};
// The following creates one object in each
// file where Initializer.h is included, but that
// object is only visible within that file:
static Initializer init;
#endif // INITIALIZER_H ///:~
The declarations for x and
y announce only that these objects exist, but they don’t allocate
storage for the objects. However, the definition for the Initializer init
allocates storage for that object in every file where the header is included.
But because the name is static (controlling visibility this time, not the
way storage is allocated; storage is at file scope by default), it is visible
only within that translation unit, so the linker will not complain about
multiple definition errors.
Here is the file containing the
definitions for x, y, and initCount:
//: C10:InitializerDefs.cpp {O}
// Definitions for Initializer.h
#include "Initializer.h"
// Static initialization will force
// all these values to zero:
int x;
int y;
int Initializer::initCount;
///:~
(Of course, a file static instance of
init is also placed in this file when the header is included.) Suppose
that two other files are created by the library user:
//: C10:Initializer.cpp {O}
// Static initialization
#include "Initializer.h"
///:~
//: C10:Initializer2.cpp
//{L} InitializerDefs Initializer
// Static initialization
#include "Initializer.h"
using namespace std;
int main() {
cout << "inside main()" << endl;
cout << "leaving main()" << endl;
} ///:~
Now it doesn’t matter which
translation unit is initialized first. The first time a translation unit
containing Initializer.h is initialized, initCount will be zero so
the initialization will be performed. (This depends heavily on the fact that the
static storage area is set to zero before any dynamic initialization takes
place.) For all the rest of the translation units, initCount will be
nonzero and the initialization will be skipped. Cleanup happens in the reverse
order, and ~Initializer( ) ensures that it will happen only
once.
This example used built-in types as the
global static objects. The technique also works with classes, but those objects
must then be dynamically initialized by the Initializer class. One way to
do this is to create the classes without constructors and destructors, but
instead with initialization and cleanup member functions using different names.
A more common approach, however, is to have pointers to objects and to create
them using new inside Initializer( ).
Technique two
Long after technique one was in use,
someone (I don’t know who) came up with the technique explained in this
section, which is much simpler and cleaner than technique one. The fact that it
took so long to discover is a tribute to the complexity of C++.
This technique relies on the fact that
static
objects inside functions are initialized the first time (only) that the function
is called. Keep in mind that the problem we’re really trying to solve here
is not when the static objects are initialized (that can be controlled
separately) but rather making sure that the initialization happens in the proper
order.
This technique is very neat and clever.
For any initialization dependency, you place a static object inside a function
that returns a reference to that object. This way, the only way you can access
the static object is by calling the function, and if that object needs to access
other static objects on which it is dependent it must call their
functions. And the first time a function is called, it forces the initialization
to take place. The order of static initialization is guaranteed to be correct
because of the design of the code, not because of an arbitrary order established
by the linker.
To set up an example, here are two
classes that depend on each other. The first one contains a bool that is
initialized only by the constructor, so you can tell if the constructor has been
called for a static instance of the class (the static storage area is
initialized to zero at program startup, which produces a false value for
the bool if the constructor has not been called):
//: C10:Dependency1.h
#ifndef DEPENDENCY1_H
#define DEPENDENCY1_H
#include <iostream>
class Dependency1 {
bool init;
public:
Dependency1() : init(true) {
std::cout << "Dependency1 construction"
<< std::endl;
}
void print() const {
std::cout << "Dependency1 init: "
<< init << std::endl;
}
};
#endif // DEPENDENCY1_H ///:~
The constructor also announces when it is
being called, and you can print( ) the state of the object to find
out if it has been initialized.
The second class is initialized from an
object of the first class, which is what will cause the
dependency:
//: C10:Dependency2.h
#ifndef DEPENDENCY2_H
#define DEPENDENCY2_H
#include "Dependency1.h"
class Dependency2 {
Dependency1 d1;
public:
Dependency2(const Dependency1& dep1): d1(dep1){
std::cout << "Dependency2 construction ";
print();
}
void print() const { d1.print(); }
};
#endif // DEPENDENCY2_H ///:~
The constructor announces itself and
prints the state of the d1 object so you can see if it has been
initialized by the time the constructor is called.
To demonstrate what can go wrong, the
following file first puts the static object definitions in the wrong order, as
they would occur if the linker happened to initialize the Dependency2
object before the Dependency1 object. Then the order is reversed to show
how it works correctly if the order happens to be “right.” Lastly,
technique two is demonstrated.
To provide more readable output, the
function separator( ) is created. The trick is that you can’t
call a function globally unless that function is being used to perform the
initialization of a variable, so separator( ) returns a dummy value
that is used to initialize a couple of global variables.
//: C10:Technique2.cpp
#include "Dependency2.h"
using namespace std;
// Returns a value so it can be called as
// a global initializer:
int separator() {
cout << "---------------------" << endl;
return 1;
}
// Simulate the dependency problem:
extern Dependency1 dep1;
Dependency2 dep2(dep1);
Dependency1 dep1;
int x1 = separator();
// But if it happens in this order it works OK:
Dependency1 dep1b;
Dependency2 dep2b(dep1b);
int x2 = separator();
// Wrapping static objects in functions succeeds
Dependency1& d1() {
static Dependency1 dep1;
return dep1;
}
Dependency2& d2() {
static Dependency2 dep2(d1());
return dep2;
}
int main() {
Dependency2& dep2 = d2();
} ///:~
The functions d1( ) and
d2( ) wrap static instances of Dependency1 and
Dependency2 objects. Now, the only way you can get to the static objects
is by calling the functions and that forces static initialization on the first
function call. This means that initialization is guaranteed to be correct, which
you’ll see when you run the program and look at the
output.
Here’s how you would actually
organize the code to use the technique. Ordinarily, the static objects would be
defined in separate files (because you’re forced to for some reason;
remember that defining the static objects in separate files is what causes the
problem), so instead you define the wrapping functions in separate files. But
they’ll need to be declared in header files:
//: C10:Dependency1StatFun.h
#ifndef DEPENDENCY1STATFUN_H
#define DEPENDENCY1STATFUN_H
#include "Dependency1.h"
extern Dependency1& d1();
#endif // DEPENDENCY1STATFUN_H ///:~
Actually, the “extern” is
redundant for the function declaration. Here’s the second header
file:
//: C10:Dependency2StatFun.h
#ifndef DEPENDENCY2STATFUN_H
#define DEPENDENCY2STATFUN_H
#include "Dependency2.h"
extern Dependency2& d2();
#endif // DEPENDENCY2STATFUN_H ///:~
Now, in the implementation files where
you would previously have placed the static object definitions, you instead
place the wrapping function definitions:
//: C10:Dependency1StatFun.cpp {O}
#include "Dependency1StatFun.h"
Dependency1& d1() {
static Dependency1 dep1;
return dep1;
} ///:~
Presumably, other code might also be
placed in these files. Here’s the other file:
//: C10:Dependency2StatFun.cpp {O}
#include "Dependency1StatFun.h"
#include "Dependency2StatFun.h"
Dependency2& d2() {
static Dependency2 dep2(d1());
return dep2;
} ///:~
So now there are two files that could be
linked in any order and if they contained ordinary static objects could produce
any order of initialization. But since they contain the wrapping functions,
there’s no threat of incorrect initialization:
//: C10:Technique2b.cpp
//{L} Dependency1StatFun Dependency2StatFun
#include "Dependency2StatFun.h"
int main() { d2(); } ///:~
When you run this program you’ll
see that the initialization of the Dependency1 static object always
happens before the initialization of the Dependency2 static object. You
can also see that this is a much simpler approach than technique
one.
You might be tempted to write
d1( ) and d2( ) as inline functions inside their
respective header files, but this is something you must definitely not do. An
inline function can be duplicated in every file in which it appears – and
this duplication includes the static object definition. Because inline
functions automatically default to internal linkage, this would result in having
multiple static objects across the various translation units, which would
certainly cause problems. So you must ensure that there is only one definition
of each wrapping function, and this means not making the wrapping functions
inline.
Alternate linkage
specifications
What happens if you’re writing a
program in C++ and you want to use a C library? If you make the C function
declaration,
float f(int a, char b);
the C++ compiler will decorate this name
to something like _f_int_char to support function overloading (and
type-safe linkage). However, the C compiler that compiled your C library has
most definitely not decorated the name, so its internal name will be
_f. Thus, the linker will not be able to resolve your C++ calls to
f( ).
The escape mechanism provided in C++ is
the alternate linkage specification, which was produced in the language
by overloading the extern
keyword. The extern is followed by a string that
specifies the linkage you want for the declaration, followed by the
declaration:
extern "C" float f(int a, char b);
This tells the compiler to give C linkage
to f( ) so that the compiler doesn’t decorate the
name. The only two types of
linkage specifications supported by the standard are “C” and
“C++,” but compiler vendors have the option of supporting
other languages in the same way.
If you have a group of declarations with
alternate linkage, put them inside braces, like this:
extern "C" {
float f(int a, char b);
double d(int a, char b);
}
extern "C" {
#include "Myheader.h"
}
Most C++ compiler vendors handle the
alternate linkage specifications inside their header files that work with both C
and C++, so you don’t have to worry about
it.
Summary
The static keyword can be
confusing because in some situations it controls the location of storage, and in
others it controls visibility and linkage of a name.
With the introduction of C++ namespaces,
you have an improved and more flexible alternative to control the proliferation
of names in large projects.
The use of static inside classes
is one more way to control names in a program. The names do not clash with
global names, and the visibility and access is kept within the program, giving
you greater control in the maintenance of your
code.
Exercises
Solutions to selected exercises
can be found in the electronic document The Thinking in C++ Annotated
Solution Guide, available for a small fee from
www.BruceEckel.com.
- Create a function with a
static variable that is a pointer (with a default argument of zero). When the
caller provides a value for this argument it is used to point at the beginning
of an array of int. If you call the function with a zero argument (using
the default argument), the function returns the next value in the array, until
it sees a “-1” value in the array (to act as an end-of-array
indicator). Exercise this function in
main( ).
- Create
a function that returns the next value in a Fibonacci sequence every time you
call it. Add an argument that is a bool with a default value of
false such that when you give the argument with true it
“resets” the function to the beginning of the Fibonacci sequence.
Exercise this function in
main( ).
- Create
a class that holds an array of ints. Set the size of the array using
static const int inside the class. Add a const int
variable, and initialize it in the constructor initializer list; make the
constructor inline. Add a static int member variable and
initialize it to a specific value. Add a static member function that
prints the static data member. Add an inline member function
called print( ) to print out all the values in the array and to call
the static member function. Exercise this class in
main( ).
- Create
a class called Monitor that keeps track of the number of times that its
incident( ) member function has been called. Add a
print( ) member function that displays the number of incidents. Now
create a global function (not a member function) containing a static
Monitor object. Each time you call the function it should call
incident( ), then print( )
member function to display the incident count. Exercise the
function in
main( ). - Modify
the Monitor class from Exercise 4 so that you can
decrement( ) the incident count. Make a class Monitor2 that
takes as a constructor argument a pointer to a Monitor1, and which stores
that pointer and calls incident( ) and print( ). In the
destructor for Monitor2, call decrement( ) and
print( ). Now make a static object of Monitor2 inside
a function. Inside main( ), experiment with calling the function and
not calling the function to see what happens with the destructor of
Monitor2.
- Make
a global object of Monitor2 and see what
happens.
- Create a
class with a destructor that prints a message and then calls
exit( ). Create a global object of this class and see what
happens.
- In
StaticDestructors.cpp, experiment with the order of constructor and
destructor calls by calling f( ) and g( ) inside
main( ) in different orders. Does your compiler get it
right?
- In
StaticDestructors.cpp, test the default error handling of your
implementation by turning the original definition of out into an
extern declaration and putting the actual definition after the definition
of a (whose Obj constructor sends information to out). Make
sure there’s nothing else important running on your machine when you run
the program or that your machine will handle faults
robustly.
- Prove that
file static variables in header files don’t clash with each other when
included in more than one cpp
file.
- Create a
simple class containing an int, a constructor that initializes the
int from its argument, a member function to set the int from its
argument, and a print( ) function that prints the int. Put
your class in a header file, and include the header file in two cpp
files. In one cpp file make an instance of your class, and in the other
declare that identifier extern and test it inside main( ).
Remember, you’ll have to link the two object files or else the linker
won’t find the
object.
- Make the
instance of the object in Exercise 11 static and verify that it cannot be
found by the linker because of
this.
- Declare a
function in a header file. Define the function in one cpp file and call
it inside main( ) in a second cpp file. Compile and verify
that it works. Now change the function definition so that it is static
and verify that the linker cannot find
it.
- Modify
Volatile.cpp from Chapter 8 to make comm::isr( ) something
that could actually work as an interrupt service routine. Hint: an interrupt
service routine doesn’t take any
arguments.
- Write and
compile a simple program that uses the auto and register
keywords.
- Create a
header file containing a namespace. Inside the namespace create
several function declarations. Now create a second header file that includes the
first one and continues the namespace, adding several more function
declarations. Now create a cpp file that includes the second header file.
Alias your namespace to another (shorter) name. Inside a function definition,
call one of your functions using scope resolution. Inside a separate function
definition, write a using directive to introduce your namespace into that
function scope, and show that you don’t need scope resolution to call the
functions from your
namespace.
- Create a
header file with an unnamed namespace. Include the header in two separate
cpp files and show that an unnamed space is unique for each translation
unit.
- Using the
header file from Exercise 17, show that the names in an unnamed namespace are
automatically available in a translation unit without
qualification.
- Modify
FriendInjection.cpp to add a definition for the friend function and to
call the function inside
main( ).
- In
Arithmetic.cpp, demonstrate that the using directive does not
extend outside the function in which the directive was
made.
- Repair the
problem in OverridingAmbiguity.cpp, first with scope resolution, then
instead with a using declaration that forces the compiler to choose one
of the identical function
names.
- In two header
files, create two namespaces, each containing a class (with all inline
definitions) with a name identical to that in the other namespace. Create a
cpp file that includes both header files. Create a function, and inside
the function use the using directive to introduce both namespaces. Try
creating an object of the class and see what happens. Make the using
directives global (outside of the function) to see if it makes any difference.
Repair the problem using scope resolution, and create objects of both
classes.
- Repair the
problem in Exercise 22 with a using declaration that forces the compiler
to choose one of the identical class
names.
- Extract the
namespace declarations in BobsSuperDuperLibrary.cpp and
UnnamedNamespaces.cpp and put them in separate header files, giving the
unnamed namespace a name in the process. In a third header file create a new
namespace that combines the elements of the other two namespaces with
using declarations. In main( ), introduce your new namespace
with a using directive and access all the elements of your
namespace.
- Create a
header file that includes <string> and <iostream> but
does not use any using directives or using declarations. Add
“include guards” as you’ve seen in the header files in this
book. Create a class with all inline functions that contains a string
member, with a constructor that initializes that string from its argument
and a print( ) function that displays the string. Create a
cpp file and exercise your class in
main( ).
- Create
a class containing a static double and long. Write a
static member function that prints out the
values.
- Create a
class containing an int, a constructor that initializes the int
from its argument, and a print( ) function to display the
int. Now create a second class that contains a static object of
the first one. Add a static member function that calls the static
object’s print( ) function. Exercise your class in
main( ).
- Create
a class containing both a const and a non-const static
array of int. Write static methods to print out the arrays.
Exercise your class in
main( ).
- Create
a class containing a string, with a constructor that initializes the
string from its argument, and a print( ) function to display
the string. Create another class that contains both const and
non-const static arrays of objects of the first class, and
static methods to print out these arrays. Exercise this second class in
main( ).
- Create a
struct that contains an int and a default constructor that
initializes the int to zero. Make this struct local to a function.
Inside that function, create an array of objects of your struct and
demonstrate that each int in the array has automatically been initialized
to zero.
- Create a
class that represents a printer connection, and that only allows you to have one
printer.
- In a header
file, create a class Mirror that contains two data members: a pointer to
a Mirror object and a bool. Give it two constructors: the default
constructor initializes the bool to true and the Mirror
pointer to zero. The second constructor takes as an argument a pointer to a
Mirror object, which it assigns to the object’s internal pointer;
it sets the bool to false. Add a member function
test( ): if the object’s pointer is nonzero, it returns the
value of test( ) called through the pointer. If the pointer is zero,
it returns the bool. Now create five cpp files, each of which
includes the Mirror header. The first cpp file defines a global
Mirror object using the default constructor. The second file declares the
object in the first file as extern, and defines a global Mirror
object using the second constructor, with a pointer to the first object.
Keep doing this until you reach the last file, which will also contain a global
object definition. In that file, main( ) should call the
test( ) function and report the result. If the result is
true, find out how to change the linking order for your linker and change
it until the result is
false.
- Repair
the problem in Exercise 32 using technique one shown in this
book.
- Repair the
problem in Exercise 32 using technique two shown in this
book.
- Without
including a header file, declare the function puts( ) from the
Standard C Library. Call this function from
main( ).
 |
|