C/C++ 静态变量初始化顺序的不确定性导致崩溃

先来看一个例子:

现在有两个cpp文件,分别为a.cpp, b.cpp,另外还有一个a.h文件。

a.h内容如下:

#include <string>
class A {
public:
	A(std::string const& name) {register(name);}
	bool register(std::string name);
}

a.cpp内容如下:

#include "a.h"
#include <map>
static std::map<std::string, int> sg_map;

bool A::register(std::string const& name) {
	sg_map.insert(std::make_pair(name, 0));
	return true;
}

b.cpp内容如下:

#include "A.h"
class B {
public:
    static A a;
};

A B::a("B");

这里会隐藏一个潜在的bug,即sg_map.insert(std::make_pair(name, 0));这一行会崩溃,并且在进入main函数之前就会出现的崩溃。原因就是:

由于静态变量初始化顺序的不确定性导致的。在C++中,不同编译单元中的静态变量的初始化顺序是未定义的。也就是说,如果你在一个编译单元中定义了一个静态变量,并在另一个编译单元中使用它,那么你不能确定这个变量在使用时是否已经被初始化。

也就是这里在b.cpp中有一个静态变量B::a初始化时调用了另一个a.cpp中的静态变量sg_map。如果B::a的初始化过程早于sg_map,那么当调用register时,sg_map还未初始化,这时如何对sg_map进行insert操作就会产生崩溃,也就是segment fault错误。上述例子在某些情况下可能并不会崩溃,比如不同的编译器,同一个编译器的不同版本,结果可能不一样。即使不会崩溃,也不建议使用全局的静态变量。

但是如果还使用sg_map的定义放到函数register中作为局部变量使用就不会有这样的问题,原因是sg_map是一个局部静态变量,它在A::register函数中被定义。局部静态变量会在第一次进入其定义的函数或语句块时被初始化。所以,当你调用A::register函数时,可以确保sg_map已经被初始化。

a.cpp

#include "a.h"
#include <map>

bool A::register(std::string const& name) {
    static std::map<std::string, int> sg_map;
	sg_map.insert(std::make_pair(name, 0));
	return true;
}

因此,为了避免这种问题,一种常见的做法是将全局静态变量改为局部静态变量,如上述所示,或者使用函数返回静态局部变量的方式来创建单例。例如:

std::map<std::string,int>& get_map() {
    static std::map<std::string, int> s_map;
    return s_map;
}

然后你可以通过调用get_map()函数来访问这个映射。

还有一种情况,如果在register中调用sg_map.empty()则不会崩溃,如下所示。这是因为std::map::empty()函数只是检查映射是否包含元素,而不需要访问映射的实际内容。即使sg_map还没有被初始化,调用empty()函数通常也不会导致崩溃。然而,当你试图在未初始化的映射中插入元素时,程序需要访问映射的内部数据结构来找到插入位置,这时如果映射还没有被初始化,就可能会导致崩溃。

a.cpp

#include "a.h"
#include <map>
static std::map<std::string, int> sg_map;

bool A::register(std::string const& name) {
	bool is_empty = sg_map.empty();
	sg_map.insert(std::make_pair(name, 0));
	return true;
}

结论:

如果使用静态变量,并且在不同的编译单元中有不同的静态变量,最好不要使用全局静态变量,而是使用局部静态变量。这样就不会因为不同的编译单元(.cpp)中的不同的静态变量初始化顺序不确定原因导致潜在崩溃问题。

在同一个编译单元中,静态变量的初始化顺序是固定,是按照静态变量定义顺序初始化的。不同的编译单元静态变量初始化顺序不确定。

这中初始化方式同样适用于全局变量,因此对于全局变量的使用也要慎重。尤其是不同编译单元的全局变量相互调用的情况应该避免出现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

space01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值