对编译器来说,静态成员变量的初始化顺序和析构顺序是一个未定义的行为
#include <string>
#include <iostream>
using namespace std;
class Base{
public:static int b;static int a;};
int Base::b = 2;
int Base::a = b + 1;int main()
{Base base;cout <<"Base::a="<< Base::a << endl;cout <<"Base::b="<< Base::b << endl;return 0;
}
6 7 18 19 行怎么修改值都一样可见静态类型成员变量的初始化顺序和编译器和有关
如下文所说:https://wenku.baidu.com/view/60a101842b4ac850ad02de80d4d8d15abe2300da.html
理解在⼀个应⽤程序内部,静态变量的构造/析构顺序。其中,对于全局静态变量,视编译器的实现⽽定(⼀种⽅式是根据字
母顺序来决定);对于有依赖关系的,那么视依赖关系⽽定。⽽对于局部静态变量来说,问题就开始复杂了。这也正是本⽂论述的重点。
⾸先,要理解编译器是如何实现局部静态变量的语法特性的。从语法上来看,局部静态变量与全局静态变量最⼤的不同在于构造时机——当
且仅当程序执⾏路径⾸次达到局部静态变量的定义处才出发构造。注意是⾸次。印象中编译器是通过添加⼀个标识变量(当然这个变量⼀定
是全局静态的)来实现的(即每次程序执⾏到时,⾸先检查这个标志变量),如此来确保调⽤时构造且只构造⼀次的特性。那么反过来看局
部静态变量的析构,编译器会维护⼀个析构函数的函数指针栈,⼀旦构造完成,就会把相应的析构函数指针放到这个栈中。当程序结束后,
由编译器⽣成的doexit函数会逐个调⽤这些析构函数,完成进程结束前的扫尾⼯作。基于此,很显然,对于分布在程序各处的静态局部变
量,其构造顺序取决于它们在程序的实际执⾏路径上的先后顺序,⽽析构顺序则正好与之相反。
很简单,不是么?可为什么说是隐藏的坑呢,问题在于:
⼀⽅⾯是因为程序的实际执⾏路径有多个决定因素(例如基于消息驱动模型的程序和多线程程序),有时是不可预知的; 另⼀⽅⾯是因为局部静态变量分布在程序代码各处,彼此直接没有明显的关联,很容易让开发者忽略它们之间的这种关系(这是最坑的地 ⽅)。
既然提出问题,那么就讨论应对之道:
(1)最简单的,避免使⽤局部静态变量,将变量的声明周期控制在开发者⼿中;
(2)如果确有需要,那么尽量确保局部静态变量之间构造和析构是彼此独⽴互不相关的,换句话说,它们可以以任意的顺序被构造和析
构;
设计模式里的单例模式就有静态变量互相引用从而系统奔溃。
测试程序
#include <string>
#include <iostream>
using namespace std;
class Log
{
public:static Log* GetInstance(){static Log oLog;return &oLog;}void Output(string strLog){cout<<strLog<<(*m_pInt)<<endl;}
private:Log():m_pInt(new int(3)){}~Log(){cout<<"~Log"<<endl;delete m_pInt;m_pInt = NULL;}int* m_pInt;
};class Context
{
public:static Context* GetInstance(){static Context oContext;return &oContext;}~Context(){Log::GetInstance()->Output(__FUNCTION__);}void fun(){Log::GetInstance()->Output(__FUNCTION__);}
private:Context(){}Context(const Context& context);
};int main(int argc, char* argv[])
{Context::GetInstance()->fun();return 0;
}
参考博客:https://www.freesion.com/article/7937607333/