2

I've read several questions here on Stack Overflow, Microsoft docs, and cplusplus.com. Some claim that exit() terminates the program normally, just as a return 0; would from main. Others claim that exit() doesn't call all the destructors, etc. So I was wondering if someone could help.

edit: As someone asked, I added some blocks of code for which I would like to terminate the program. I use C++20

HKEY newKey;

    RegOpenKey(HKEY_CURRENT_USER, R"(Software\Microsoft\Windows\CurrentVersion\Run)", &newKey);
    LONG result = RegSetValueEx(newKey, value, 0, REG_SZ, (LPBYTE)filePath, lstrlen(filePath));
    RegCloseKey(newKey);

    if(result != ERROR_SUCCESS){
        MessageBox(nullptr, "Could not add to startup", "Error", MB_ICONERROR);
        exit(1);
    }
int i = line.find(':');

    if(i == std::string::npos){
        MessageBox(nullptr, "File is incorrectly formatted", "Error", MB_ICONERROR);
        exit(1);
    }
info.open(infoPath);

    if(info.fail()){
        MessageBox(nullptr, "info.txt did not open", "Error", MB_ICONERROR);
        exit(1);
    }

I link the posts I've read about this:

How to end C++ code

https://cplusplus.com/forum/beginner/4589/

How to exit program execution in C++?

https://learn.microsoft.com/en-us/cpp/cpp/program-termination?view=msvc-170

https://cplusplus.com/reference/cstdlib/exit/

Thanks in advance

GioByte10
  • 40
  • 5
  • Beware of `_exit()`. – Ben Voigt Aug 01 '22 at 21:23
  • 1
    What have you read that would make you think that `exit()` calls all the destructors? How would it even find all the objects to destroy them? – Sneftel Aug 01 '22 at 21:23
  • @Sneftel: For example, the Microsoft docs say "The exit, _Exit and _exit functions terminate the calling process. The exit function calls destructors for thread-local objects, then calls—in last-in-first-out (LIFO) order—the functions that are registered by atexit and _onexit, and then flushes all file buffers before it terminates the process. The _Exit and _exit functions terminate the process without destroying thread-local objects or processing atexit or _onexit functions, and without flushing stream buffers." – Ben Voigt Aug 01 '22 at 21:24
  • Exactly. The thread locals are a borderline side effect, and stack-allocated objects have no chance. Not that that necessarily makes anything “unsafe”. – Sneftel Aug 01 '22 at 21:26
  • @Sneftel: You're making stuff up, your described behavior would violate the Standard `[basic.start.term]` – Ben Voigt Aug 01 '22 at 21:29
  • https://learn.microsoft.com/en-us/cpp/intrinsics/fastfail?view=msvc-170 – Hans Passant Aug 01 '22 at 21:31
  • @BenVoigt In what way? Where does that section guarantee that stack-allocated objects have their destructors called by `exit`? – Sneftel Aug 01 '22 at 21:32
  • @Sneftel: "stack-allocated" is an implementation detail. Destructors for objects of automatic storage duration are NOT called (found and added that quote too). Destructors for objects of static storage duration ARE called. – Ben Voigt Aug 01 '22 at 21:40
  • @BenVoigt Right. So what bit do you feel I was making up? – Sneftel Aug 01 '22 at 22:01
  • _So I was wondering if someone could help_ It would help if we know what you actually _want_ to happen. Oftentimes, a quick exit is what's desired. Then again, sometimes you want to do something more graceful. – Paul Sanders Aug 01 '22 at 22:40

3 Answers3

3

Some claim that exit() terminates the program normally, just as a return 0; would from main

std::exit gets called anyway, as part of standard application lifecycle. Quote from CPPReference:

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.


Others claim that exit() doesn't call all the destructors, etc

It's true, but it doesn't cancel the first statement. I.e. if destructors of you classes don't have any side effects that can survive the program, it doesn't matter whether local were destroyed properly or not, since entire memory of your process is freed after program termination. std::exit however calls destructors of objects with static and thread local storage duration:

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::atexit are executed concurrently

The Dreams Wind
  • 8,416
  • 2
  • 19
  • 49
2

It is completely unreasonable to assume a program is going to properly fall all the way out of main() during any form of error handling. In the case of a we should not / can not go any further, professional programs call exit() all the time.

However, if you have objects that NEED to be cleaned up, then you need to implement an atexit handler. See:

https://en.cppreference.com/w/cpp/utility/program/atexit

Joseph Larson
  • 8,530
  • 1
  • 19
  • 36
  • destructors are processed in the same manner as atexit handlers. See https://eel.is/c++draft/basic.start.term#1 – Ben Voigt Aug 01 '22 at 21:29
2

For a call to the C++ standard function ::exit or std::exit, destructors will be called for objects with static storage duration. Note this doesn't apply to other termination functions like _exit(), abort(), TerminateProcess() etc.

The old simple verbiage ([basic.start.term]) from C++03:

Destructors for initialized objects of static storage duration (declared at block scope or at namespace scope) are called as a result of returning from main and as a result of calling exit

In contrast, destructors for automatic storage duration objects are not called by exit() ([basic.start.main]):

Calling the function void exit(int); declared in <cstdlib> terminates the program without leaving the current block and hence without destroying any objects with automatic storage duration

The contrast with abort() is also quite insightful:

Calling the function void abort(); declared in <cstdlib> terminates the program without executing destructors for objects of automatic or static storage duration and without calling the functions passed to atexit().

For dynamically-allocated objects, naturally destructors are only called if the cleanup process for static (and thread-static) storage objects explicitly destroys such objects (typically because they are owned in a smart pointer).

New verbiage: https://eel.is/c++draft/basic.start.term still provides for calling destructors, but now contains new rules to handle threads and thread-local objects instead of just static and automatic storage class.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 2
    Cppreference states pretty explicitly "Stack is not unwound: destructors of variables with automatic storage duration are not called.". "Destructors will be called" seems like an overstatement. – Nathan Pierson Aug 01 '22 at 21:39
  • @NathanPierson: Took me a couple minutes to find the actual rule in the real Standard, because it was oddly in a different section. – Ben Voigt Aug 01 '22 at 21:41