Static
elements from C
In
both C and C++ the keyword
static
has two basic meanings, which unfortunately often step on each other’s
toes:
- Allocated
once at a fixed address; that is, the object is created in a special
static
data area
rather
than on the stack each time a function is called. This is the concept of
static
storage.
- Local
to a particular translation unit (and class scope in C++, as you will see
later). Here,
static
controls the
visibility
of
a name, so that name cannot be seen outside the translation unit or class. This
also describes the concept of
linkage,
which determines what names the linker will see.
This
section will look at the above meanings of
static
as they were inherited from C.
static
variables inside functions
Normally,
when you create a variable inside a function, the compiler allocates storage
for that variable each time the function is called by moving the stack pointer
down an appropriate amount. If there is an initializer for the variable, the
initialization is performed each time that sequence point is passed.
Sometimes,
however, you want to retain a value between function calls. You could
accomplish this by making a global variable, but that variable would not be
under the sole control of the function. C and C++ allow you to create a
static
object inside a function; the storage for this object is not on the stack but
instead in the program’s static storage area. This object is initialized
once the first time the function is called and then retains its value between
function invocations. For example, the following function returns the next
character in the string each time the function is called:
//: C10:Statfun.cpp
// Static vars inside functions
#include <iostream>
#include "../require.h"
using namespace std;
char onechar(const char* string = 0) {
static const char* s;
if(string) {
s = string;
return *s;
}
else
require(s, "un-initialized s");
if(*s == '\0')
return 0;
return *s++;
}
char* a = "abcdefghijklmnopqrstuvwxyz";
int main() {
// Onechar(); // require() fails
onechar(a); // Initializes s to a
char c;
while((c = onechar()) != 0)
cout << c << endl;
} ///:~
The
static
char* s
holds its value between calls of
onechar( )
because its storage is not part of the stack frame of the function, but is in
the static storage area of the program. When you call
onechar( )
with a
char*
argument,
s
is assigned to that argument, and the first character of the string is
returned. Each subsequent call to
onechar( )
without
an argument produces the default value of zero for
string,
which indicates to the function that you are still extracting characters from
the previously initialized value of
s.
The function will continue to produce characters until it reaches the null
terminator of the string, at which point it stops incrementing the pointer so
it doesn’t overrun the end of the string.
But
what happens if you call
onechar( )
with no arguments and without previously initializing the value of
s?
In the definition for
s,
you could have provided an initializer,
but
if you do not provide an initializer for a static variable of a built-in type,
the compiler guarantees that variable will be initialized to zero (converted to
the proper type) at program start-up. So in
onechar( ),
the first time the function is called,
s
is zero. In this case, the
if(!s)
conditional will catch it.
The
above initialization for
s
is very simple, but initialization for static objects (like all other objects)
can be arbitrary expressions involving constants and previously declared
variables and functions.
static
class objects inside functions
The
rules are the same for static objects of user-defined types, including the fact
that some initialization is required for the object. However, assignment to
zero has meaning only for built-in types; user-defined types must be
initialized with constructor calls. Thus, if you don’t specify
constructor arguments when you define the static object, the class must have a
default constructor.
For example,
//: C10:Funobj.cpp
// Static objects in functions
#include <iostream>
using namespace std;
class X {
int i;
public:
X(int ii = 0) : i(ii) {} // Default
~X() { cout << "X::~X()" << endl; }
};
void f() {
static X x1(47);
static X x2; // Default constructor required
}
int main() {
f();
} ///:~
The
static objects of type
X
inside
f( )
can be initialized either with the constructor argument list or with the
default constructor. This construction occurs the first time control passes
through the definition, and only the first time.
Static
object destructors
Destructors
for static objects (all objects with static storage, not just local static
objects as in the above example) are called when
main( )
exits or when the Standard C library function
exit( )
is
explicitly called,
main( )
in most implementations calls
exit( )
when it terminates. This means that it can be dangerous to call
exit( )
inside a destructor because you can end up with infinite recursion. Static
object destructors are
not
called if you exit the program using the Standard C library function
abort( ). You
can specify actions to take place when leaving
main( )
(or calling
exit( ))
by using the Standard C library function
atexit( ).
In this case, the functions registered by
atexit( )
may
be called before the destructors for any objects constructed before leaving
main( )
(or calling
exit( )). Destruction
of static objects occurs
in the reverse order of initialization. However, only objects that have been
constructed are destroyed. Fortunately, the programming system keeps track of
initialization order and the objects that have been constructed. Global objects
are always constructed
before
main( )
is entered, so this last statement applies only to static objects that are
local to functions. If a function containing a local static object is
never called, the constructor for that object is never executed, so the
destructor is also not executed. For example,
//: C10:StaticDestructors.cpp
// Static object destructors
#include <fstream>
using namespace std;
ofstream out("statdest.out"); // Trace file
class Obj {
char c; // Identifier
public:
Obj(char cc) : c(cc) {
out << "Obj::Obj() for " << c << endl;
}
~Obj() {
out << "Obj::~Obj() for " << c << endl;
}
};
Obj a('a'); // Global (static storage)
// Constructor & destructor always called
void f() {
static Obj b('b');
}
void g() {
static Obj c('c');
}
int main() {
out << "inside main()" << endl;
f(); // Calls static constructor for b
// g() not called
out << "leaving main()" << endl;
} ///:~
In
Obj,
the
char
c
acts as an identifier so the constructor and destructor can print out
information about the object they’re working on. The
Obj
a
is a global object, so the constructor is always called for it before
main( )
is entered, but the constructors for the
static
Obj b
inside
f( )
and the
static
Obj C
inside
g( )
are called only if those functions are called.
To
demonstrate which constructors and destructors are called, inside
main( )
only
f( )
is called. The output of the program is
Obj::Obj() for a
inside main()
Obj::Obj() for b
leaving main()
Obj::~Obj() for b
Obj::~Obj() for a
The
constructor for
a
is called before
main( )
is entered, and the constructor for
b
is called only because
f( )
is called. When
main( )
exits, the destructors for the objects that have been constructed are called in
reverse order of their construction. This means that if
g( )
is
called, the order in which the destructors for
b
and
c
are called depends on whether
f( )
or
g( )
is
called first.
Notice
that the trace file
ofstream
object
out
is also a static object.
It is important that its definition (as opposed to an
extern
declaration) appear at the beginning of the file, before there is any possible
use of
out.
Otherwise you’ll be using an object before it is properly initialized.
In
C++ the constructor for a global static object is called before
main( )
is entered, so you now have a simple and portable way to execute code before
entering
main( )
and to execute code with the destructor after exiting
main( ).
In C this was always a trial that required you to root around in the compiler
vendor’s assembly-language startup code.
Controlling
linkage
Ordinarily,
any name at
file
scope
(that
is, not nested inside a class or function) is visible throughout all
translation units in a program. This is often called
external
linkage
because
at link time the name is visible to the linker everywhere, external to that
translation unit. Global variables and ordinary functions have external linkage.
There
are times when you’d like to limit the visibility of a name. You might
like to have a variable at file scope so all the functions in that file can use
it, but you don’t want functions outside that file to see or access that
variable, or to inadvertently cause name clashes with identifiers outside the
file.
An
object or function name at file scope that is explicitly declared
static
is local to its translation unit (in the terms of this book, the
.cpp
file where the declaration occurs); that name has
internal
linkage.
This means you can use the same name in other translation units without a name
clash.
One
advantage to internal linkage is that the name can be placed in a header file
without worrying that there will be a clash at link time. Names that are
commonly placed in header files, such as
const
definitions and
inline
functions, default to internal linkage. (However,
const
defaults to internal linkage only in C++; in C it defaults to external
linkage.) Note that linkage refers only to elements that have addresses at
link/load time; thus, class declarations and local variables have no linkage.
Confusion
Here’s
an example of how the two meanings of
static
can
cross over each other. All global objects implicitly have static storage class,
so if you say (at file scope),
then
storage for
a
will be in the program’s static data area, and the initialization for
a
will occur once, before
main( )
is entered. In addition, the visibility of
a
is global, across all translation units. In terms of visibility, the opposite of
static
(visible only in this translation unit) is
extern,
which explicitly states that the visibility of the name is across all
translation units. So the above definition is equivalent to saying
all
you’ve done is change the visibility, so
a
has internal linkage. The storage class is unchanged – the object resides
in the static data area whether the visibility is
static
or
extern. Once
you get into local variables,
static
stops altering the visibility (and
extern
has no meaning) and instead alters the storage class.
With
function names,
static
and
extern
can only alter visibility, so if you say,
it’s
the same as the unadorned declaration
it
means
f( )
is visible only within this translation unit; this is sometimes called
file
static.
Other
storage class specifiers
You
will see
static
and
extern
used commonly. There are two other storage class specifiers that occur less
often. The
auto
specifier
is almost never used because it tells the compiler that this is a local
variable. The compiler can always determine this fact from the context in which
the variable is defined, so
auto
is redundant.
A
register
variable
is a local (
auto)
variable, along with a hint to the compiler that this particular variable will
be heavily used, so the compiler ought to keep it in a register if it can.
Thus, it is an optimization aid. Various compilers respond differently to this
hint; they have the option to ignore it. If you take the address of the
variable, the
register
specifier will almost certainly be ignored. You should avoid using
register
because the compiler can usually do a better job at of optimization than you.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru