Am avut 371721 vizite de la lansarea siteului.




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:

  1. 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.
  2. 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;

But if you say instead,

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();

and if you say,

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.

Home   |   Web Faq   |   Radio Online   |   About   |   Products   |   Webmaster Login

The quality software developer.™
© 2003-2004 ruben|labs corp. All Rights Reserved.
Timp de generare a paginii: 17583 secunde
Versiune site: 1.8 SP3 (build 2305-rtm.88542-10.2004)