Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Quick context: I'm seeing errors on program shutdown, that stem from dependencies between global members (::sigh::, I know, I know). One global variable's destructor might refer to another global -- and if that one's already been destructed, things get bad.

But here's a particular case where I just don't know whether behavior is well-defined: a static variable inside a function. Can I rely on the function behaving consistently even during program shutdown? Or is it possible that the static member will be destroyed, and the function will run anyway, without creating a new one?

Here's a toy example demonstrating what I'm interested in:

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;
    }
};

Let's imagine I have a global instance of Logger. The enum2str map in Logger::FormatMessage is a static variable, so at some point during program shutdown, it will be destroyed.

By standard, can this cause my program to crash on shutdown? Is enum2str inherently unreliable during shutdown? Or is there some handling of this -- for example, if enum2str is invalid at some point, perhaps a new static instance will be created?

(I am not interested in relying on destruction order between objects, e.g. where I declare the global Logger instance.)

like image 714
Ziv Avatar asked Oct 22 '18 08:10

Ziv


People also ask

Do static variables remain even after closing the program?

Yes. Just for a little more context, static data is created and placed in your applications Stack Data segment (memory) and will remain there for the life time of the application. You can always reference it and do not need to worry about it being deallocated (unless it is an optional and you nil it out).

Do static variables go out of scope?

Static variables in C++ have program lifetime thus they will not go out of scope so no race conditions. However, this function can only be called by a single thread at a time since the static variables are shared over all invocations of the function which may not be a suitable solution.

What happens to a static variable that is defined within a method of a class?

Static variables in methods i.e. you cannot use a local variable outside the current method which contradicts with the definition of class/static variable. Therefore, declaring a static variable inside a method makes no sense, if you still try to do so, a compile time error will be generated.

At what point during a program's execution are automatic external static variables initialized?

Initialization of static variables A static variable in a block is initialized only one time, prior to program execution, whereas an auto variable that has an initializer is initialized every time it comes into existence.


2 Answers

Without seeing more of your program, the general answer is yes. The destruction of that static map can cause your program to have undefined behavior:

[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.

In general, static objects are destroyed in reverse order to being initialized. So hypothetically, if you have a static object that is initialized early, before the map in the logger, and it logs something in its own destructor, you'll get undefined behavior.

like image 92
StoryTeller - Unslander Monica Avatar answered Sep 28 '22 04:09

StoryTeller - Unslander Monica


I am not interested in relying on destruction order between objects

You should, since that is exactly what determines whether FormatMessage is safe to call during the program shutdown.

The code that runs during shutdown is destructors of static objects, and function registered with atExit.

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

You cannot rely on it in general, but you can rely on it in certain circumstances.

It is safe to rely on static objects in atExit, so it is safe to call FormatMessage there. Unless you can guarantee the order of destruction between particular static object s, and enum2str, it is not safe to use FormatMessage in the destructor of s.

Static objects are guaranteed to be destroyed in reverse order of their construction. Therefore you can rely on enum2str existing during destruction of the subset of static objects whose constructors call FormatMessage, because calling FormatMessage in the constructor ensures that enum2str will have been constructed before that dependent static object has finished constructing.

There is a trick to relying on a static object regardless of the order of destruction: Never destruct the dependee. This can be achieved by using a static function scope pointer to a dynamically allocated object, that you intentionally never delete. As a drawback, this will trigger diagnostics in memory analyzers and may increase the blood pressure of your dogmatic co-workers.

like image 38
eerorika Avatar answered Sep 28 '22 04:09

eerorika