依赖静态对象地址安全吗

Is it safe to rely on static object adresses?

本文关键字:安全 地址 对象 静态 依赖      更新时间:2023-10-16

假设我有这样的代码:

#include <iostream>
#include <unordered_map>
#include <functional>
using const_string_ref = std::reference_wrapper<const std::string>;
namespace std
{
template<>
struct hash<const_string_ref>
{
size_t operator()(const const_string_ref& ref) const
{
return std::hash<std::string>()(ref);
}
};
bool    operator==(const const_string_ref& lhs,
const const_string_ref& rhs)
{
return (lhs.get() == rhs.get());
}
}
class test
{
public:
void process(const std::string& action)
{
(this->*(ACTIONS_PROCESSORS_MAP_.at(action)))();
}
private:
using action_processor = void (test::*)();
using actions_map = std::unordered_map<const_string_ref, action_processor>;
private:
static const std::string FIRST_KEY_;
static const std::string SECOND_KEY_;
static const actions_map ACTIONS_PROCESSORS_MAP_;
private:      
void first_action()
{
std::cout << "first works" << std::endl;
}
void second_action()
{
std::cout << "second works" << std::endl;
}
};
const std::string test::FIRST_KEY_ = "first";
const std::string test::SECOND_KEY_ = "second";
const test::actions_map test::ACTIONS_PROCESSORS_MAP_ =
{{std::cref(FIRST_KEY_), &test::first_action},
{std::cref(SECOND_KEY_), &test::second_action}};

int main()
{
test t;
t.process("first");
t.process("second");
return 0;
}

主要问题是:

我是否保证在进入main函数时,test::ACTIONS_PROCESSORS_MAP_中用作密钥的reference_wrapper中包含的引用将分别被正确初始化为对test::FIRST_KEY_test::SECOND_KEY_的有效引用,而与静态顺序初始化无关?

这个问题可以概括为:

指向satatic对象的指针/引用是否在这些对象初始化之前就有效(即地址是否会在某个时刻更改)?

是的,一旦分配了对象的存储,即使尚未初始化,也可以安全地获取对象的地址。静态对象的存储在程序期间持续,因此您可以随时获取地址。

当然,在对象的生存期之外访问对象本身是不允许的。

值得一提的是,在单个翻译单元中对非局部静态变量(不在类模板数据成员中)的初始化是有序的(§3.6.2/2):

显式专用类模板静态数据成员的定义[…不是这个]。其他类模板静态数据成员[…不是这个]。其他具有静态存储持续时间的非本地变量已下令初始化。在单个翻译单元中定义的有序初始化变量应按照其在翻译单元中的定义顺序进行初始化。

因此,您知道FIRST_KEY_将在SECOND_KEY_之前初始化,ACTIONS_PROCESSORS_MAP_将在之前初始化。

但即使不是这样,也没关系。由于std::string具有非平凡的结构,因此从分配存储到开始使用寿命之间还有一段时间。在这个时候,有一个指向对象的指针是可以的,只要你不使用它做某些事情。你的代码不会对它做任何事情,而是存储指针-你是安全的。§3.8/5:

在对象的生存期开始之前,但在分配对象将占用的存储之后,或者在对象的生命期结束之后,在重用或释放对象所占用的存储之前,可以使用任何指向对象将要或曾经所在的存储位置的指针,但只能以有限的方式使用。

有限的方法基本上是不涉及访问对象的任何方法。事实上,甚至可以通过指针执行间接操作,只要它最终不会进行左值到右值的转换(本质上代表从内存中读取对象)。