After my previous post about static members in various programming language, I decide to write a post about the word static
in depth. static
is a key word in multiple programming languages, with multiple distinct but related meanings.
The history
The static
keyword in C originally served as a “storage specifier”. Although C is a high level language, it is designed for system programming which allows fine-grained control of memory, and the language constructs can be easily mapped to system infrastructure (hence called “high-level assembler”). Therefore, we need to know how objects are stored in the memory. Objects in C have one of the following storage duration:
- Automatic storage duration: The object comes to existence automatically when it comes to the scope, and disappears when it leaves the scope.
- Static storage duration: The object exists for the whole lifetime of the program.
- Dynamic storage duration: The object is created and destroyed manually by the programmer.
In terms of implementation, the three kinds of storage duration maps perfectly to three parts of the memory:
- The stack: When a scope is entered, the stack pointer is decreased to allocate space for objects of automatic storage duration automatically. Therefore, these objects are stored on the stack.
- The program image: Objects with static storage duration is stored inside the program image (normally the data section), and is loaded and available once the program starts executing.
- The heap:
malloc()
allocates memory from the heap, which is used by objects with dynamic storage duration.
The keyword auto
or static
are used to specify an object having automatic or static storage duration respectively, while objects with dynamic storage duration are created with malloc()
and destroyed with free()
respectively. Because all objects declared in functions have automatic storage duration by default, the auto
is basically useless in C, and replaced with another completely unrelated meaning in C++11. However, objects declared in the file scope always have static storage duration because they always exists when the program is running.
static
may be applied to objects declared to file scope as well, however, it has a different meaning here. A static
object declared in file scope means that it has internal linkage, i.e. it can only be referenced inside the same translation unit (i.e. the file with all the included stuff), and invisible to other translation units, where a file-scope object declared without static
can be referenced by other translation units by means of extern
declaration. To summarise, objects declared in file scope can be:
- without linkage specifier: declared and defined with external linkage
- with
extern
: declared to reference a definition elsewhere - with
static
: declared and defined with internal linkage
Therefore, static
in C has two usages:
- in file scope: to declare an object with internal linkage rather than external linkage
- in function scope: to declare an object with static storage duration rather than automatic storage duration
Derived meaning of static
Use in function scope
The file scope meaning of static
in C is normally not relevant in most other programming languages because it deals with linkage, which is an implementation detail of the C programming language. However, the meaning in function scope has some interesting properties, which forms the basis of static
in other programming languages.
Consider the following C program:
#include <stdio.h> int test(void) { static int count = 0; ++count; return count; } int main(void) { for (int i = 0; i < 10; ++i) { printf("%d\n", test()); } return 0; }
Inside function test
, count
is declared to have static duration duration, which means the object exists throughout the whole program lifetime, i.e. “shared” and “preserved” between function invocations. Therefore, count
effectively acts as a counter which keeps track how many times the function is called. Using static
variables inside function is therefore considered bad programming practice because it introduces hidden global state of the program, just like using global variables. (The common thing between a static
local variable and a global variable is that, they exist throughout the program runtime. These variables are shared across the whole program, which are hard to test and break thread safety).
Therefore, the static
keyword is adopted by other programming languages to declare variables which should be preserved and shared across function invocations.
Class scope
In C++, apart from usage in file and function scope, static
can also be used to declare class members.
static
member objects
If static
is used to declare an object member, that object member become having static storage duration, rather than contained inside the class object inheriting the storage duration. A static object member is not defined at the point of declaration. A definition must be provided in namespace scope outside the class, mainly because the class declaration is normally included in multiple translation units, but the definition can only appear once in the whole program.
Consider the following C++ program:
#include <iostream> using std::cout; struct Test { static int count; Test() { ++count; } }; int Test::count; int main() { Test a, b; cout << a.count << '\n'; }
In the above program, count
is declared as static
inside Test
, which means it has static storage duration, existing for the whole program lifetime, i.e. shared and preserved across class instances, same as the effect of static
on variables in function scope. Being “shared across class instances” can be reworded to “not depending on a particular class instance”, unlike regular members. Therefore, a.count
can be replaced with Test::count
in the above code, removing the reference to an instance. In particular, a.count
is considered bad practice because it can produce unexpected effects when inheritance is involved (more details in my previous article) and we should always use a class expression to access static members.
Furthermore, using static
member objects is also considered bad practice, because it is just another form of global variable (especially public static member objects) shared across the whole program.
static
member functions
The sense that a static
member “does not depend on a particular class instance” is used for member function (i.e. method) as well. If a member function is declared with static
, it can be called without an instance, leading to the limitation that a static
member function
- cannot use
this
- cannot use non-static members directly
Although the term “storage duration” does not apply to functions (functions are not objects) in C++, the meaning of static
when applied to a class method traces its root to the usage of C to declare a static variable, which is shared across the whole program.
static
member classes
In Java, even a member class can be declared static
as well!
A class can have classes as members (called inner classes) in Java, where instances of non-static inner classes can only exist inside an instance of its outer class. In implementation terms, an instance of an inner class holds an implicit pointer to the outer class as OuterClass.this
, and you can access all instance members of the outer class inside the inner class implicitly.
Here is an example, in Java, how a non-static inner class is used:
class Test { public class InnerClass { public void increaseOuterX() { ++x; } public Test getOuterClass() { return Test.this; } } public int x; } public class Main { public static void main(String[] args) { Test.InnerClass a = new Test().new InnerClass(); Test.InnerClass b = new Test().new InnerClass(); Test.InnerClass c = b.getOuterClass().new InnerClass(); a.increaseOuterX(); b.increaseOuterX(); c.increaseOuterX(); System.out.println(a.getOuterClass().x); System.out.println(b.getOuterClass().x); System.out.println(c.getOuterClass().x); } }
In the main method, b
and c
are inner class objects inside the same outer class object, and a
is inside another outer class object. Note that the InnerClass
objects are created inside an OuterClass
objects, and getOuterClass()
returns the containing outer class instance of the inner class object.
By declaring the inner class static
, it no longer depends on an outer class instance, i.e. OuterClass.this
can no longer be used. In such case, the inner class instances can and must be created independently without an outer class instance, and the inner class can no longer access non-static members of the containing class, just like a static member function. However, a non-static member class cannot contain a static member because it creates confusion. Should static members of a non-static inner class shared among inner class instances created from different outer class instances, or shared among inner class instances created from the same outer class instance only? For example, the following is illegal:
class Test { public class InnerClass { public static int innerStaticMember; public Test getOuterClass() { return Test.this; } } } public class Main { public static void main(String[] args) { Test.InnerClass a = new Test().new InnerClass(); Test.InnerClass b = new Test().new InnerClass(); Test.InnerClass c = b.getOuterClass().new InnerClass(); b.innerStaticMember = 3; System.out.println(c.innerStaticMember); // should be 3 System.out.println(a.innerStaticMember); // 0 or 3? } }