21

Consider the following code:

#include <cstdlib>
struct Foo {
    ~Foo() {
        std::exit(0);
    }
} foo;
int main() {
}

It compiles and terminates with zero successfully for me both on my Linux (GCC, Clang) and Windows (Visual Studio). However, when compiled with MSYS2's GCC on Windows (g++ (Rev2, Built by MSYS2 project) 10.3.0), it enters an infinite recursion and dies from stack overflow. This can be checked by adding some debug output right before std::exit(); I did not add it initially to avoid thinking about the destruction of std::cout.

Does any C++ standard have anything to say about such behavior? Is it well-defined/implementation-defined/undefined/etc and why?

For instance, [support.start.term]/9.1 of some recent draft says the following about std::exit's behavior:

First, objects with thread storage duration and associated with the current thread are destroyed. Next, objects with static storage duration are destroyed and functions registered by calling atexit are called. See [basic.start.term] for the order of destructions and calls.

which refers to [basic.start.term]/1, I guess:

Constructed objects ([dcl.init]) with static storage duration are destroyed and functions registered with std​::​atexit are called as part of a call to std​::​exit ([support.start.term]). The call to std​::​exit is sequenced before the destructions and the registered functions.

I don't see any immediate restriction on calling std::exit in a destructor.

Sidenote: please refrain from commenting that "this code is bad", "thou shalt not use destructors in global objects" (which you probably should not) and probing for the XY problem in the comments. Consider this an academic question from a curious student who knows a much better solution to their original problem, but have stumbled upon this quirk while exploring the vast meadows of C++.

Boann
  • 48,794
  • 16
  • 117
  • 146
yeputons
  • 8,478
  • 34
  • 67
  • 2
    At the very least there is the distinct possibility of things going wrong in the handling of the `atexit()` stack. It is unlikely that it cleans up after itself, since it's not *meant* to be handled twice -- but this construct likely does. Same for the closing of streams... – DevSolar Oct 27 '21 at 11:42
  • 2
    In C it's explicitly called out as undefined behavior to call exit more than once (returning from main counts as calling exit) in annex J.2: "The program calls the exit or quick_exit function more than once, or calls both functions (7.22.4.4, 7.22.4.7)" (Yes I know C isn't C++ but this restriction is likely inherited by C++). – user786653 Oct 27 '21 at 12:05
  • 2
    Combine with 16.2.2 (library.c) from the C++ standard, and I think you have your answer (unless someone else can find opposing wording) – user786653 Oct 27 '21 at 12:15
  • It looks that same question asked many times (I found 4) and sharing only one and would like to give more space to community to find them. Jerry Coffin Answer is the exact match to this question. – Nadeem Taj Oct 28 '21 at 09:02

1 Answers1

43

[basic.start.main]/4:

If std​::​exit is called to end a program during the destruction of an object with static or thread storage duration, the program has undefined behavior.

Language Lawyer
  • 3,378
  • 1
  • 12
  • 29