16

What does the standard have to say about function-local static initialization during program exit? EDIT: For clarity, I mean a case as in the code example - the local static b is constructed after another static a is constructed(so supposedly b should be destroyed before a), but b is also constructed during a's destructor, so should it be destroyed immediately? after? UB?

I didn't manage to find any reference to this matter.

I'd like to know if such a case is UB, or should it have some defined behaviour?

The following code is an example of that:

struct B{};

void foo()
{
    static B b;
}

struct A
{
    ~A() { foo(); }
};

int main()
{
    static A a;
    
    return 0;
}

As you can see, the destructor of A would occur during program exit(since it has static storage), and it'll try to construct a B static instance.

I'm more interested in C++17 if it makes any difference in this subject.

theroyn
  • 506
  • 5
  • 16
  • space for static variables is already reserved by the program as they are basically global variables that can only be accessed in certain scopes. AFAIK this code is 100% fine and should do what you expect it to do. – NathanOliver Jun 22 '21 at 18:38
  • also could be relevant: https://timsong-cpp.github.io/cppwp/basic.stc.static#1 – NathanOliver Jun 22 '21 at 18:40
  • This section but Standard changes so taking time to read it fully https://en.cppreference.com/w/cpp/utility/program/exit – Richard Critten Jun 22 '21 at 18:41
  • I have not seen any indication that such usage is undefined behavior in the Standard. However, I would not be surprised if particular implementation might have issues here - which would be grounds for filing a bug with implementors. – SergeyA Jun 22 '21 at 18:46
  • 1
    From answers here, behavior was unspecified before C++11 and destruction in reverse-order guaranteed since. https://stackoverflow.com/questions/3143180/how-to-do-static-de-initialization-if-the-destructor-has-side-effects-and-the-ob – Drew Dormann Jun 22 '21 at 18:56
  • 1
    @SergeyA The issue came up when I experienced a deadlock during the destruction of a singleton(eww, not my design choice). Digging further I found a function-local static being initialized in a function called deep inside the destructor. I'm trying to understand if it was illegal to call said function. – theroyn Jun 22 '21 at 19:17
  • @DrewDormann this case is more confusing because the local static "b" is constructed after another static "a" is constructed(so supposedly "b" should be destroyed before "a"), but "b" is also constructed during "a"'s destructor, so should it be destroyed immediately? after? UB? – theroyn Jun 22 '21 at 19:23
  • 1
    This para _"...If the completion of the constructor or dynamic initialization for thread-local or static object A was sequenced-before thread-local or static object B, the completion of the destruction of B is sequenced-before the start of the destruction of A..."_ but we are already in the destructor of A so we contradict ourselves - https://en.cppreference.com/w/cpp/utility/program/exit – Richard Critten Jun 22 '21 at 19:45
  • 2
    Standard Reference [basic.start.term](https://timsong-cpp.github.io/cppwp/basic.start.term#3) has the same issue as above – Richard Critten Jun 22 '21 at 19:51
  • @RichardCritten hmm but it's once again "the completion of the destructor of the second is sequenced before the initiation of the destructor of the first.".. – theroyn Jun 22 '21 at 19:57
  • @theroyn yup as far as I can research we are inside of a contradiction. So I am leaning towards a Standard defect - BUT am very happy to be proved wrong. – Richard Critten Jun 22 '21 at 19:59
  • @RichardCritten Actually part (4) is talking about a very similiar case, but only when the local static object was destroyed first, too bad.. I wonder if it's simply missing in the standard? – theroyn Jun 22 '21 at 20:01

2 Answers2

2

I do not see a conflict here, given that foo() is only ever called from the destructor of A.

From [stmt.dcl]/5 [emphasis mine]:

A block-scope object with static or thread storage duration will be destroyed if and only if it was constructed. [ Note: [basic.start.term] describes the order in which block-scope objects with static and thread storage duration are destroyed. — end note ]

And, from [basic.start.term]/4 [emphasis mine]:

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.

As b in foo() is (in this particular example) not constructed until the destruction of a in main(), [basic.start.term]/4 will not be violated as b is yet to be destroyed at this point.

If foo() is invoked prior to the destruction of a in main(), it's a different story.

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • I'm not saying there's a conflict. Actually, there's just no information. The standard doesn't say anything about construction inside a destructor besides ```[basic.start.term]/4```, which doesn't apply to this case since ```foo()``` wasn't called before. I think that's a defect, something about this should be mentioned in the standard. – theroyn Jul 04 '21 at 10:55
-2

Since Object B was created by A's destructor, it will be destroyed before program exits. I added print statement in your code to make it look like this:

#include <iostream>

struct B
{
    ~B()
    {
        std::cout << "Called B's Destructor...!";
    }
};

void foo()
{
    static B b;
}

struct A
{
    ~A() {
        foo();
        std::cout << "Called A's Destructor...!\n";
    }
};

int main()
{
    static A a;
    
    return 0;
}

It prints the following:

Called A's Destructor...!
Called B's Destructor...!

The bottomline is, the static variables will be destroyed at the end of the program (local or global), and in the order it was created.