1. 了解变量析构顺序的必要性
大多数时间里,我们更关注的是变量的初始化顺序,因为我们需要确保使用到的变量都是初始化好的变量。然而,当项目变大、变复杂的时候,我们可能就会开始考虑程序的退出问题,尤其是多线程程序的退出,变量的析构顺序可能会影响到程序是否能优雅、安全地退出。
2. 全局(静态)变量的析构顺序
我们都知道C++规范规定,变量析构的顺序和构造的顺序是相反的。对于在同一个编译单元内的全局(静态)变量的初始化顺序,与他们声明的顺序是相同的,而析构的顺序则与初始化的顺序是相反的;对于不在同一个编译单元的全局(静态)变量的初始化顺序是不确定的。
3. 局部静态变量的析构顺序
相对复杂的是局部静态变量的初始化与析构流程。对于局部静态变量,大家都知道的一点就是初始化是发生在函数第一次运行的时候,所以我们可以推导出,局部静态变量的初始化肯定是晚于全局(静态)变量的,所以其析构肯定是早于全局(静态)变量的。
4. 析构顺序的分析
前面我们直接给出了结论,下面用地段代码来看一下局部静态变量晚于全局(静态)变量析构的原因:
#include <stdio.h>
#include <string>
#include <string.h>
class A
{
public:
A()
{
}
A(const std::string &name)
{
name_ = name;
printf("A of %s\n", name_.c_str());
}
~A()
{
printf("~A of %s\n", name_.c_str());
}
private:
std::string name_;
};
A a("global a");
void test()
{
static A local_a("local a");
}
int main()
{
test();
return 0;
}
然后我们编译成汇编语言,会看到,当程序定义一个结构体变量时,会在定义结束后调用 __cxa_atexit,来注册程序exit时调用的析构函数。全局(静态)变量位于代码段,构造会在进入main函数之前,对于局部静态变量,构造函数会在main函数调用func时,注册晚于全局变量,所以析构的调用就会早于全局变量的析构函数。
如果想要控制一个局部静态变量的析构,晚于一个全局(静态)变量,则可以将对函数的调用,放到全局(静态)变量的构造函数内调用
#include <stdio.h>
#include <string>
#include <string.h>
void func();
class A
{
public:
A()
{
}
A(const std::string &name)
{
name_ = name;
printf("A of %s\n", name_.c_str());
func();
}
~A()
{
printf("~A of %s\n", name_.c_str());
}
private:
std::string name_;
};
class B
{
public:
B()
{
}
B(const std::string &name)
{
name_ = name;
printf("B of %s\n", name_.c_str());
}
~B()
{
printf("~B of %s\n", name_.c_str());
}
private:
std::string name_;
};
A a("global a");
void func()
{
static B lb("local b");
}
int main()
{
return 0;
}
最后补充一个结论,析构函数的顺序:
局部静态变量 ==> attribute((destructor)) ==> 全局变量