std::exit(3)
β¨ std::exit(3) β the C++ Standard Library glow-up edition β¨
okay bestie, so you know how a program has to end at some point? yeah. this is literally the function that does it. it's std::exit from the C++ Standard Library, and honestly, she's highkey the main character of program termination. let's get into it π
πΈ NAME πΈ
std::exit β yes, she's named std::exit, and she's come to end your program in the most orderly way possible. fr fr.
π¦ Synopsis π¦
Bestie, she lives in <cstdlib>:
void exit( int exit_code ); (until C++11)
[[noreturn]] void exit( int exit_code ); (since C++11)
And what she does? She causes normal program termination to occur. periodt. π
π DESCRIPTION π
Like, several cleanup steps are performed. this is the whole ritual, okay? the way that she methodically tidies up before she clocks out? iconic. π§
- Objects with static storage duration are destroyed, and functions registered by calling
std::atexitare called. This is the big one, so buckle up:
a) Non-local objects with static storage duration are destroyed in the reverse order of the completion of their constructor. yes, literally backwards. LIFO energy. β¨
b) Functions registered with std::atexit are called in the reverse order of their registration, except that a function is called after any previously registered functions that had already been called at the time it was registered. (ikyk, bestie β she's doing the nuanced ordering thing.)
c) For each function f registered with std::atexit and each non-local object obj of static storage duration:
(until C++11)
- if
fis registered before the initialization ofobj,fwill only be called after the destruction ofobj; - if
fis registered after the initialization ofobj,fwill only be called before the destruction ofobj.
d) For each local object obj with static storage duration, obj is destroyed as if a function calling the destructor of obj were registered with std::atexit at the completion of the constructor of obj.
(since C++11) β okay so here's the concurrent glow-up version:
- The destructors of objects with thread local storage duration that are associated with the current thread, the destructors of objects with static storage duration, and the functions registered with
std::atexitare executed concurrently, while maintaining the following guarantees:
a) The last destructor for thread-local objects is sequenced-before the first destructor for a static object.
b) 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.
c) If the completion of the initialization of a static object A was sequenced-before the call to std::atexit for some function F, the call to F during termination is sequenced-before the start of the destruction of A.
d) If the call to std::atexit for some function F was sequenced-before the completion of initialization of a static object A, the start of the destruction of A is sequenced-before the call to F during termination.
e) If a call to std::atexit for some function F1 was sequenced-before the call to std::atexit for some function F2, then the call to F2 during termination is sequenced-before the call to F1.
And like, in the above:
- π¨ If any function registered with
atexitor any destructor of static/thread-local object throws an exception,std::terminateis called. no cap, she will not be tolerating that. π - If the compiler opted to lift dynamic initialization of an object to the static initialization phase of non-local initialization, the sequencing of destruction honors its would-be dynamic initialization.
- If a function-local (block-scope) static object was destroyed and then that function is called from the destructor of another static object and the control flow passes through the definition of that object (or if it is used indirectly, via pointer or reference), the behavior is undefined. π bestie do NOT do this.
- If a function-local (block-scope) static object was initialized during construction of a subobject of a class or array, it is only destroyed after all subobjects of that class or all elements of that array were destroyed.
- All C streams are flushed and closed. bye streams, love you, close the door on your way out π«Ά
- Files created by
std::tmpfileare removed. clean up your mess, queen π - Control is returned to the host environment. And here's the tea:
- If
exit_codeis0orEXIT_SUCCESS, an implementation-defined status indicating successful termination is returned. - If
exit_codeisEXIT_FAILURE, an implementation-defined status indicating unsuccessful termination is returned. - In other cases, an implementation-defined status value is returned.
- If
β οΈ Stack is not unwound: destructors of variables with automatic storage duration are not called. like, she's not doing that for you. π€·ββοΈ
π Relationship with the main function π
Returning from the main function β either by a return statement or by reaching the end of the function β performs the normal function termination (calls the destructors of the variables with automatic storage durations) and then executes std::exit, passing the argument of the return statement (or 0 if implicit return was used) as exit_code.
so like, main is the entry vibe and std::exit is the exit vibe. they're literally connected. π₯
π Parameters π
exit_codeβ the exit status of the program. that's it, that's the whole parameter. she's simple like that.
β Return value β
(none) β she's [[noreturn]] since C++11, bestie, she does not come back. periodt. π
π Example π
// Run this code
#include <cstdlib>
#include <iostream>
struct Static
{
~Static()
{
std::cout << "Static destructor\n";
}
};
struct Local
{
~Local()
{
std::cout << "Local destructor\n";
}
};
Static static_variable; // Destructor of this object *will* be called
void atexit_handler()
{
std::cout << "atexit handler\n";
}
int main()
{
Local local_variable; // Destructor of this object will *not* be called
const int result = std::atexit(atexit_handler); // Handler will be called
if (result != 0)
{
std::cerr << "atexit registration failed\n";
return EXIT_FAILURE;
}
std::cout << "test\n";
std::exit(EXIT_FAILURE);
std::cout << "this line will *not* be executed\n";
}
Output:
test
atexit handler
Static destructor
like, notice how local_variable never got its destructor called? yeah that's the whole stack-not-unwound thing. and the atexit handler and Static destructor did run. slay. β¨
π§ Defect reports π§
The following behavior-changing defect reports were applied retroactively to previously published C++ standards. (she got a little patch note, okay?)
LWG 3, applied to C++98 β Behavior as published: during cleanup, the behavior was unclear when (1) a function is registered withstd::atexitor (2) a static local object is initialized. Correct behavior: made clear. (we love clarity π)
π See also π
abortβ causes abnormal program termination (without cleaning up) (function)atexitβ registers a function to be called onstd::exit()invocation (function)quick_exitβ causes quick program termination without completely cleaning up (C++11) (function)at_quick_exitβ registers a function to be called onstd::quick_exitinvocation (C++11) (function)- C documentation for
exit
that's the whole page bestie, you're officially an std::exit expert now π
β¨
with love from the C++ Standard Library, source: cppreference.com, dated 2024.06.10 π¦πΈπ
π¦ where this page comes from
- nixpkgs unstable: stdman 2024.07.05
- opensuse-leap 16.0: stdman 2024.07.05-bp160.1.9 Β· MIT
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms