本文为探究static变量构造、析构时机,从而整理了相关的进程启动销毁流程。最后介绍由static变量销毁机制导致的bug,引入google针对此问题设置的编程规范。
一、一个进程的启动销毁流程:
- 加载程序和库:当程序开始执行时,操作系统将程序加载到内存中,并加载所有需要的库。
- 初始化全局变量(包括全局静态变量):在加载程序时,编译器会为所有全局变量分配内存,调用它们的构造函数。
- 执行main函数:在全局变量构造完成后,程序会执行main函数。
- 初始化局部变量(包括局部静态变量):运行到局部静态变量位置,构造一次。
- 执行return 0:当main()函数执行到return语句时,程序会将返回值传递给exit()函数,并且调用所有已注册的函数,最终退出程序。
二、全局变量、static全局变量、static局部变量构造析构顺序
- main函数入口前:全局变量和全局静态变量构造
同一个文件内的全局对象,初始化的顺序与他们声明的顺序是一致的(销毁的顺序则相反)。而对于不同文件间的全局对象,c++ 标准并没有明确规定它们之间的初始化(销毁)顺序应该怎样,因此实现上完全由编译器自己决定。编译器会维护一个析构函数的函数指针栈,一旦构造完成,就会把相应的析构函数指针放到这个栈中。 - main函数内:初始化static局部变量,同时将变量的析构函数存入编译器维护的指针表。
- return 0后:析构所有全局变量与局部静态变量,由编译器生成的exit函数会逐个调用(按照注册顺序)函数指针栈中的析构函数。
三、静态变量导致的bug
静态变量A析构函数中调用了静态变量B的函数:由于析构顺序不一定,调用时B可能依据被析构了,因此导致了程序崩溃。
四、google针对静态对象的编程规范
不允许声明类的静态变量和类的全局变量,这是因为这样会引起构造函数和析构函数执行顺序的混乱。
参考资料:
1、Google C++编程规范 – 第二十八条 -《静态变量和全局变量》
2、C++全局对象,静态局部对象,自动局部对象构造析构顺序
3、__do_global_dtors_aux和__do_global_ctors_aux作用