C++ 静态变量析构顺序的分析与控制

1. 了解变量析构顺序的必要性

  大多数时间里,我们更关注的是变量的初始化顺序,因为我们需要确保使用到的变量都是初始化好的变量。然而,当项目变大、变复杂的时候,我们可能就会开始考虑程序的退出问题,尤其是多线程程序的退出,变量的析构顺序可能会影响到程序是否能优雅、安全地退出。

2. 全局(静态)变量的析构顺序

  我们都知道C++规范规定,变量析构的顺序和构造的顺序是相反的。对于在同一个编译单元内的全局(静态)变量的初始化顺序,与他们声明的顺序是相同的,而析构的顺序则与初始化的顺序是相反的;对于不在同一个编译单元的全局(静态)变量的初始化顺序是不确定的。

3. 局部静态变量的析构顺序

  相对复杂的是局部静态变量的初始化与析构流程。对于局部静态变量,大家都知道的一点就是初始化是发生在函数第一次运行的时候,所以我们可以推导出,局部静态变量的初始化肯定是晚于全局(静态)变量的,所以其析构肯定是早于全局(静态)变量的。

4. 析构顺序的分析

  前面我们直接给出了结论,下面用地段代码来看一下局部静态变量晚于全局(静态)变量析构的原因:

#include 
#include 
#include 
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 
#include 
#include 

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)) ==> 全局变量

你可能感兴趣的:(C++ 静态变量析构顺序的分析与控制)