我可以依赖函数范围的静态变量来调用程序关闭期间调用的方法吗?

Can I rely on a function-scoped static variable for a method called during program shutdown?

快速上下文:我看到程序关闭时出现错误,这些错误源于全局成员之间的依赖关系(::sigh::,我知道,我知道)。一个全局变量的析构函数可能引用另一个全局变量——如果那个已经被破坏,事情就会变得糟糕。

但这是一个我不知道行为是否定义明确的特殊情况:函数内部的一个静态变量。我可以依赖函数的行为一致吗?在程序关闭期间?还是有可能静态成员将被销毁,并且该函数将 运行 无论如何,而不创建一个新成员?

这是一个演示我感兴趣内容的玩具示例:

class Logger
{
public:
    enum class Severity { DEBUG, INFO, WARNING, ERROR };
    void Log(Severity sev, const std::string& msg)
    {
        LogImpl(FormatMessage(sev, msg));
    }

    Logger() { Log(Severity::INFO, "Logger active"); }
    ~Logger() { Log(Severity::INFO, "Logger inactive"); }

protected:
    static std::string FormatMessage(Severity sev, const std::string& msg)
    {
        static const std::map<Severity, std::string> enum2str {
            {Severity::DEBUG, "DEBUG"},
            {Severity::INFO, "INFO"},
            {Severity::WARNING, "WARNING"},
            {Severity::ERROR, "ERROR"}
        };

        // Throws or crashes if enum2str is invalid, or uninitialized:
        return "[" + enum2str[sev] + "] " + msg;
    }
    void LogImpl(const std::string& msg)
    {
        std::cout << msg << std::endl;
    }
};

假设我有一个 Logger 的全局实例。 Logger::FormatMessage中的enum2str映射是一个静态变量,所以在程序关闭的某个时候,它会被销毁。

按照标准,这会导致我的程序在关机时崩溃吗? enum2str 在关机期间是否天生不可靠?或者对此有一些处理——例如,如果 enum2str 在某个时候无效,也许会创建一个新的静态实例?

(我对依赖对象之间的销毁顺序不感兴趣,例如我声明全局 Logger 实例的地方。)

没有看到更多你的程序,一般的答案是肯定的。破坏该静态映射可能会导致您的程序出现未定义的行为:

[basic.start.term]

3 If the completion of the constructor or dynamic initialization of an object with static storage duration strongly happens before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first [...]

4 If a function contains a block-scope object of static or thread storage duration that has been destroyed and the function is called during the destruction of an object with static or thread storage duration, the program has undefined behavior if the flow of control passes through the definition of the previously destroyed block-scope object. Likewise, the behavior is undefined if the block-scope object is used indirectly (i.e., through a pointer) after its destruction.

一般来说,静态对象的销毁顺序与初始化顺序相反。所以假设,如果你有一个静态对象,它在记录器中的映射之前提前初始化,并且它在它自己的析构函数中记录一些东西,你会得到未定义的行为。

I am not interested in relying on destruction order between objects

你应该这样做,因为这正是决定 FormatMessage 在程序关闭期间调用是否安全的因素。

关机期间运行的代码是静态对象的析构函数,并且是用atExit注册的函数。

Can I rely on a function-scoped static variable for a method called during program shutdown?

一般不能依赖,但在某些情况下可以依赖。

atExit中依赖静态对象是安全的,所以在那里调用FormatMessage是安全的。除非你能保证特定静态对象 senum2str 之间的销毁顺序,否则在 s.[=24= 的析构函数中使用 FormatMessage 是不安全的]

静态对象保证以其构造的相反顺序销毁。因此,您可以依赖 enum2str 在销毁其构造函数调用 FormatMessage 的静态对象子集期间存在,因为在构造函数中调用 FormatMessage 可确保之前构造 enum2str该依赖静态对象已完成构造。

无论销毁顺序如何,都依赖静态对象有一个技巧:永远不要销毁依赖者。这可以通过使用指向动态分配对象的静态函数作用域指针来实现,您有意从不删除该对象。作为一个缺点,这将触发内存分析器中的诊断,并可能增加你的教条同事的血压。