C++ essentials 之 static 关键字

时间:2022-10-25 04:33:19

extraction from The C++ Programming Language, 4th. edition, Bjarne Stroustrup

1. If no initializer is specified, a global, namespace, local static, or static member (collectively called static objects) is initialized to {} of the appropriate type.

2. We classify objects based on their lifetimes:

  • Automatic: Unless the programmer specifies otherwise, an object declared in a function is created when its definition is encountered and destroyed when its name goes out of scope. Such objects are sometimes called automatic objects. In a typical implementation, automatic objects are allocated on the stack; each call of the function gets its own stack frame to hold its automatic objects.
  • Static: Objects declared in global or namespace scope and statics declared in functions or classes are created and initialized once (only) and "live" until the program terminates. Such objects are called static objects. A static object has the same address throughout the life of a program execution. Static objects can cause serious problems in a multi-thread program because they are shared among all threads and typically require locking to avoid data races.
  • Free store: Using the new and delete operators, we can create objects whose lifetimes are controlled directly.
  • Temporary objects (e.g., intermediate results in a computation or an object used to hold a value for a reference to const argument): their lifetime is determined by their use. If they are bound to a reference, their lifetime is that of the reference; otherwise, they "live" until the end of the full expression of which they are part. A full expression is an expression that is not part of another expression. Typically, temporary objects are automatic.
  • Thread-local objects; that is, objects declared thread_local: such objects are created when their thread is and destroyed when their thread is.

Static and automatic are traditionally referred to as storage classes.

Array elements and nonstatic class members have their lifetimes determined by the object of which they are part.

3. An array can be allocated statically, on the stack, and on the free store. For example:

    int a1[10];                  // 10 ints in static storage
void f(){
int a2[20];
int *p = new int[40]; // 40 ints on the free store
// ...
}

4. A string literal is statically allocated so that it is safe to return one from a function. For example

    const char* error_message(int i){
// ...
return "range error"
}

5. [10.4.5] (??) The address of a statically allocated object, such as a global variable, is a constant. However, its value is assigned by the liker, rather than the compiler, so the compiler cannot know the value of such an address constant. That limits the range of constant expressions of pointer and reference type. For example:

    constexpr const char *p1="asdf";
constexpr const char *p2=p1; // OK
constexpr const char *p2=p1+2; // error: the compiler does not know the value of p1
constexpr char c=p1[2]; // OK, c=='d'; the compiler knows the value pointed to by p1

6. [11.2.2] (??) To deallocate space allocated by new, delete and delete[] must be able to determine the size of the object allocated. This implies that an object allocated using the standard implementation of new will occupy slightly more space than a static object. At a minimum, space is needed to hold the object's size. Usually two or more words per allocation are used for free-store management. Most modern machines use 8-byte words. This overhead is not significant when we allocate many objects or large objects, but it can matter if we allocate lots of small obejects (e.g., ints) on the free store.

7. A member function may be specified as static, indicating that it is not associated with a particular object.

8. [12.1.8] A name defined in a function is commonly referred to as a local name. A local variable or constant is initialized when a thread of execution reaches its definition. Unless declared static, each invocation of the function has its own copy of the variable. If a local variable is declared static, a single, statically allocated object object will be used to represent that variable in all calls of the function. It will be initialized only the first time a thread of execution reaches its definition (rather than at compile time). For example:

    void f(int a){
while(a--){
static int n=0; //initialized once
int x=0;
cout<<"n == "<<n++<<", x == "<<x++<<'\n';
}
}

A static local variable allows the function to preserve information between calls without introducing a global variable that might be accessed and corrupted by other functions.

Initialization of a static local variable does not lead to a data race unless you enter the function containing it recursively or a deadlock occurs. That is, the C++ implementation must guard the initialization of a local static variable with some kind of lock-free construct (e.g., a call_once;). The effect of initializing a local static recursively is undefined. For example:

    int fn(int n){
static int n1=n; // OK
static int n2=f(n-1)+1; //undefined
return n;
}

A static local variable is useful for avoiding order dependencies among nonlocal variables.

9. A variable defined without an initializer in the global or a namespace scope is initialized by default. This is not the case for non-static local variables or the objects created on the free store.

10. A name that can be used in translation units different from the one in which it was defined was said to have external linkage. A name that can be referred to only in the translation unit in which it is defined is said to have internal linkage. For example:

    static int x1=1;    //internal linkage
const char x2='a'; //internal linkage

When used in namespace scope (including the global scope), the keyword static (somewhat illogically) means "not accessible from other files" (i.e., internal linkage). If you wanted x1 to be accessible from other source files ("have external linkage"), you should remove the static. The key word const implies default internal linkage, so if you wanted x2 to have external linkage, you need to precede its definition with extern:

    int x1=1;    // external linkage
extern const char x2='a'; // external linkatge

Names that a linker does not see, such as the names of local variables, are said to have no linkage.

11. In principle, a variable defined outside any function (that is, global, namespace, and class static variables) is initialized before main() is invoked. Such nonlocal variables in a translation unit are initialized in their definition order. If such variable has no explicit initializer, it is by default initialized to the default for its type.

12. A variable that is part of a class, yet is not part of an object of that class, is called a static member. There is exactly one copy of a static member instead of one copy per object, as for ordinary non-static members. Similarly, a function that needs access to members of a class, yet doesn't need to be invoked for a particular object, is called a static member function.

A static member can be referred to like any other member. In addition, a static member can be referred to without mentioning an object. Instead, ins name is qualified by the name of its class.

if used, a static member--function or data member--must be defined somewhere. The keyword static is not repeated in the definition of a static member.

In multi-threaded code, static data member require some kind of locking or access discipline to avoid race conditions. Since multi-threading is now very common, it is unfortunate that use of static data members was quite popular in older code. Older code tends to use static members in ways that imply race conditions.

13. A static class member is statically allocated rather than part of each object of the class. Generally, the static member declaration acts as a declaration for a definition outside the class. For example:

    class Node{
// ...
static int node_count; // declaration
}; int Node::node_count=0; // definition

However, for a few simple special cases, it is possible to initialize a static member in the class declaration. The static member must be a const of an integral or enumeration type, or a constexpr of a literal type, and the initializer must be a constant-expression. For example:

    class Curious{
public:
static const int c1=7; // OK
static int c2=11; // error: not const
const int c3=13; // OK but not static
static const int c4=sqrt(9); //error: in-class initializer not constant
static const float c5=7.0; //error: in-class not integral (use constexpr rather than const)
// ...
};

If (and only if) you use an initialized member in a way that requires it to be stored as an object in memory, the member must be (uniquely) defined somewhere. The initializer may not be repeated:

    const int Curious::c1;    // don't repeat initializer here
const int *p = &Curious::c1; // OK: Curious::c1 has been defined

Note that const int Curious::c1; is definition of Curious::c1 which is declared in Curious as static const int c1;.