Inapoi
Inainte
Cuprins
10: Name Control
Creating names is a fundamental
activity in programming, and when a project gets large, the number of names can
easily be overwhelming.
C++ allows you a great deal of control
over the creation and visibility of names, where storage for those names is
placed, and linkage for names.
The static
keyword was overloaded in C before people knew what the
term “overload” meant, and C++ has added yet another meaning. The
underlying concept with all uses of static seems to be “something
that holds its position” (like static electricity), whether that means a
physical location in memory or visibility within a file.
In this chapter, you’ll learn how
static controls storage and visibility, and an improved way to control
access to names via C++’s namespace feature. You’ll also find
out how to use functions that were written and compiled in
C.
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 local to a 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
When you create a local 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 then 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 data area. This object is initialized only 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 array each time the function is called:
//: C10:StaticVariablesInfunctions.cpp
#include "../require.h"
#include <iostream>
using namespace std;
char oneChar(const char* charArray = 0) {
static const char* s;
if(charArray) {
s = charArray;
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 array is returned.
Each subsequent call to oneChar( ) without an argument
produces the default value of zero for charArray, 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 character array, at which point it
stops incrementing the pointer so it doesn’t overrun the end of the
array.
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,
static char* s = 0;
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 initialization above 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.
You should be aware that the function
above is very vulnerable to multithreading problems; whenever you design
functions containing static variables you should keep multithreading issues in
mind.
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:StaticObjectsInFunctions.cpp
#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 (that is,
all objects with static storage, not just local static objects as in the example
above) are called when main( ) exits or when the Standard C library
function
exit( )
is explicitly called. In most implementations, main( ) just
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( )).
Like ordinary destruction, destruction of
static objects
occurs
in the reverse order of initialization. However, only objects that have been
constructed are destroyed. Fortunately, the C++ development tools keep track of
initialization order and the objects that have been constructed. Global objects
are always
constructed
before main( ) is entered and destroyed as main( )
exits, but 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, 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 – since it is
defined outside of all functions, it lives in the static storage
area. 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 that 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),
int a = 0;
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 definition above is equivalent to
saying
extern int a = 0;
static int a = 0;
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 instead alters the storage
class.
If you declare what appears to be a local
variable as extern, it means that the storage exists elsewhere (so the
variable is actually global to the function). For example:
//: C10:LocalExtern.cpp
//{L} LocalExtern2
#include <iostream>
int main() {
extern int i;
std::cout << i;
} ///:~
//: C10:LocalExtern2.cpp {O}
int i = 5;
///:~
With function names (for non-member
functions), static and extern can only alter visibility, so if you
say
extern void f();
it’s the same as the unadorned
declaration
void f();
static void f();
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. auto is
short for “automatic” and it refers to the way the compiler
automatically allocates storage for the 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 of optimization than
you.
 |
|