Static Keyword in C++

Static Variables and Functions

当变量或者函数被声明为 static 后,我们就称其为静态变量/函数。那么静态代表了什么?在进程的虚拟内存空间里,我们看到,当变量被声明为 static 后,它会被放在数据段(初始化非 0 )或 BSS 段(初始化为 0 /未初始化)中。这意味着静态变量的生命周期会一直持续到程序结束。也被称作 static storage duration 。

#include <iostream>

int global_var; // Un-initialized, will be placed in the BSS segment
static int static_var; // Un-initialized, also placed in the BSS segment

static void static_func() {
    global_var++;
    static_var++;
}

// Non-static function accessible from other translation units
// Increments static_var
void func() {
    static_var++;
}

int main() {
    int local_var; // Un-initialized local variable, will be placed on the stack

    std::cout << global_var << std::endl
              << static_var << std::endl;
    return 0;
}

那静态函数有什么用呢?这就不得不提到 static 的另一个特性:内部链接性。

Internal Linkage

我们说被声明为 static 的静态函数和变量具有内部链接性,什么意思呢?就是说我们定义的静态变量/函数只在定义它们的翻译单元(translation unit)内可见。一个翻译单元是由一个源文件及其直接或间接包含的所有头文件组成。编译器会独立编译每个翻译单元,最后链接器会将这些编译好的翻译单元合并成一个可执行程序。

我们用一个例子说明:

// main.cpp
#include <iostream>
#include "static_unit.hpp"

int main() {
    // static_unit_func(); // ld error, no function definition
    static_func(); // Calls static_func() defined in static_unit.hpp
    func(); // Calls func() which is defined in static_unit.cpp
    return 0;
}
// static_unit.hpp
extern int global_var; // Declares that global_var is defined in another translation unit.

static int static_var; // Declaration

// Function declarations
static void static_unit_func();
static void static_func();
void func();

static void static_func(){ // No naming conflict, no linker error
	global_var += 10;
	static_var += 10;
	std::cout << "global_var shared by all the tranlation unit : " << global_var << std::endl
              << "static_var specific owning by main.cpp       : " << static_var << std::endl;
}

在这个例子中, main.cppstatic_unit.hpp 是一个翻译单元。 #include 预编译指令的作用就是把被 included 的文件全部复制到源文件中。这里的 static_unit.hpp 作为 static_unit.cpp 的接口。包含一些变量和函数的声明,在编译的时候提醒编译器我们有这个变量/函数。

static_unit.cpp 是另外一个翻译单元。待会当你运行程序后,你会发现静态变量/函数只能在相同的翻译单元中被使用。

// static_unit.cpp
#include <iostream>
// #include "static_unit.cpp" // Be commented

int global_var; // Uninitialized, will be placed in the BSS segment.
static int static_var = 0; // Definition

static void static_unit_func(){ // A static_unit.cpp specific function
}

static void static_func() {
    global_var++;
    static_var++;
}

// Non-static function accessible from other translation units. (An interface)
// Increments both global_var and static_var.
void func() {
    global_var++;
    static_var++;
    std::cout << "global_var shared by all the tranlation unit : " << global_var << std::endl
              << "static_var specific owning by static_unit.cpp: " << static_var << std::endl;
}

运行上面的代码,你会发现尽管我们在不同的翻译单元内同时定义了一个 static_func() 函数,但链接时并没有引发任何的命名冲突。而且每个翻译单元中的静态变量只能由翻译单元内的函数进行访问。不难发现,内部链接性能够规避不同单元之间的命名冲突,确保变量或函数在其他翻译单元中不可见。

运行结果如下:

global_var shared by all the tranlation unit : 10
static_var specific owning by main.cpp       : 10
global_var shared by all the tranlation unit : 11
static_var specific owning by static_unit.cpp: 1

Static inside a Class: Different Semantics?

在类中,我们还有静态成员变量和静态成员函数。和前面我们了解到的 static 语义不同,在类中,static 关键字用于声明这些成员并不受类实例的约束。换言之,静态成员变量/函数是被所有实例所共享的。

比如,我们有下面的例子:

// example.hpp

class Example{
public:
	static int s_var;
	static int get_s_var();
	Example();
	~Example();
};
// main.cpp
#include <iostream>
#include "example.hpp"
int main(){
	Example e1;
	Example e2;
	{
		Example e3;
		Example e4;
		std::cout << "We now have " << Example::get_s_var() << " instance." << std::endl;
	}
	std::cout << "We now have " << Example::get_s_var() << " instance." << std::endl;
	std::cout << "I can read the static data member: " << Example::s_var << std::endl;
}
// example.cpp
#include "example.hpp"

int Example::s_var = 0;
int Example::get_s_var(){
	return s_var; // Equals return Example::s_var;
}
Example::Example(){
	s_var++;
}
Example::~Example(){
	s_var--;
}

输出结果:

We now have 4 instance.
We now have 2 instance.

我们发现,即使类成员变量/函数的定义在 example.cpp 这个文件域(file scope)中,和 main.cpp 是独立的翻译单元,但是我们仍然能在 main.cpp 中访问得到静态的成员变量和成员函数。也就是说,静态的成员函数和成员变量并不具有内部链接性(internal linkage)。

Static Member Functions

静态成员函数只能访问静态成员变量。因为静态成员函数没有 this 指针,所以不能访问非静态的成员变量。这是一个例子:

class Example{
public:
	int var;
	static int s_var;
	static int s_func();
};
int Example::s_var = 0;
int Example::s_func(){
	var++; // invalid use of member 'Example::var' in static member function
	s_var++; 
	return s_var;
}
int main(){

	return 0;
}

由于 var 不是静态成员变量,所以编译会出错。

Static Data Members and Local Static Data

静态成员变量相当于一个带有访问属性的全局静态变量,namespace 是类的类名。和类外的静态变量一样,它们都具有 static storage duration 。静态成员变量存储在静态存储区(通常是 .data 段或 .bss 段),但它的作用域仍然是类的作用域(命名空间)。

因为静态成员变量是在类的所有实例之间共享的,而类的定义通常在头文件中。如果在类内部初始化静态成员变量,这样的初始化将在每个包含这个头文件的翻译单元中重复进行,从而导致链接时出现多重定义错误。

Local Static Data

在 local scope {} 内定义的局部 static 变量的生命周期和全局 static 变量一样。但它也具有局部的访问属性,也就是说,只有在 local scope 内部才能访问到这个静态变量。

Singleton

class Singleton
{
public:
	static Singleton& Get
	{
		static Singleton* s_Instance = nullptr;
		return *s_Instance; 
	}
};