2021年03月31日 周三 天气晴 【不悲叹过去,不荒废现在,不惧怕未来】



本文目录

  • 1. 编译时初始化
  • 2. 加载时初始化
  • 3. 运行时初始化
  • 4. static初始化的原理
  • 总结



1. 编译时初始化

如果静态变量本身是基本数据类型(POD),且初始化值是常量,并且是全局变量,那么这个初始化过程是在编译期间完成的。

static int a = 10;
static char strArray[] = "hello! world";

2. 加载时初始化

程序被加载时立即进行的初始化。这个初始化发生在main函数之前。即使程序任何地方都没访问过该变量,仍然会进行初始化,因此形象地称之为"饿汉式初始化"。

(1)静态变量是一个基本数据类型,但是初始值不是常量

static int *p = new int[1024];

int x = 3;
int y = 4;
static int z = x + y;

(2)静态变量是一个类对象,这种情况下即使是使用常量初始化,也是加载时初始化

static std::string str = "Hello world !"; // string是一个类

class MyClass {
public:	
	MyClass();    
	MyClass(int a, int b)
;};

static MyClass* MyClass1 = new MyClass(); // MyClass也是一个类
static MyClass MyClass2;

3. 运行时初始化

这个初始化发生在变量第一次被引用
也就是说,从程序执行模型角度看,程序所在进程空间中,哪个线程先访问了这个变量,就是哪个线程来初始化这个变量。因此,相对于加载初始化来说,这种初始化是把真正的初始化动作推迟到第一次被访问时,因而形象地称为"懒汉式初始化"。

int myfunc()
{     	
    static std::string msg = "hello world !";    //运行时初始化
}

4. static初始化的原理

例1:
int main()
{
    for(int x = 5; x < 10; x++)
    {
        static int y = x;	//第一次被引用时初始化,并且只初始化一次
        cout << "x = " << x << ", y = " << y << endl;
    }
    return 0;
}

输出结果:
x = 5, y = 5
x = 6, y = 5
x = 7, y = 5
x = 8, y = 5
x = 9, y = 5
例2:
int main()
{
    for(int x = 5; x < 10; x++)
    {
        static int y = x;
        cout << "x = " << x << ", y = " << y << endl;

        int *p = &y;
        p++;
        *p = 0;
    }
    return 0;
}

输出结果:
x = 5, y = 5
x = 6, y = 6
x = 7, y = 7
x = 8, y = 8
x = 9, y = 9

通过两个例子的结果我们可以知道,静态变量的初始化就是通过静态变量后面的一个32位内存位来做记录,以标识这个静态变量是否已经初始化。每次运行到当前位置,会先去判断这个地址:
如果不是1,就给它赋值1,然后给变量赋值;
如果是1,直接跳过赋值代码块这样它就做到了只赋值一次的效果;

在例2中我们每次都将这个值赋值为0,所以程序就一直认为变量一直没有被初始化过,并每次都初始化。该操作并非一个原子操作,因此从代码逻辑角度来说,static变量并不具有“线程安全”性能。

总结

(1)如果是编译时和加载时初始化,是不会存在线程安全这个问题的。因为这两种初始化一定发生在Main函数执行之前,这个时候尚未进入程序运行空间,而这些初始化一定是在单线程环境下操作的。
(2)如果是运行时初始化,因为无法保证访问这个静态变量一定只会从某个特定的线程中被访问,因此会存在"线程安全"的问题。