The std::abort(), std::quick_exit(), and std::_Exit() functions are used to terminate the program in an immediate fashion. They do so without calling exit handlers registered with std::atexit() and without executing destructors for objects with automatic, thread, or static storage duration. How a system manages open streams when a program ends is implementation-defined [ISO/IEC 9899:1999]. Open streams with unwritten buffered data may or may not be flushed, open streams may or may not be closed, and temporary files may or may not be removed. Because these functions can leave external resources, such as files and network communications, in an indeterminate state, they should be called explicitly only in direct response to a critical error in the application. (See ERR50-CPP-EX1 for more information.)

The std::terminate() function calls the current terminate_handler function, which defaults to calling std::abort().

The C++ Standard defines several ways in which std::terminate() may be called implicitly by an implementation [ISO/IEC 14882-2014]:

  1. When the exception handling mechanism, after completing the initialization of the exception object but before activation of a handler for the exception, calls a function that exits via an exception ([except.throw], paragraph 7)
  2. When a throw-expression with no operand attempts to rethrow an exception and no exception is being handled ([except.throw], paragraph 9)
  3. When the exception handling mechanism cannot find a handler for a thrown exception ([except.handle], paragraph 9)
  4. When the search for a handler encounters the outermost block of a function with a noexcept-specification that does not allow the exception ([except.spec], paragraph 9)
  5. When the destruction of an object during stack unwinding terminates by throwing an exception ([except.ctor], paragraph 3)
  6. When initialization of a nonlocal variable with static or thread storage duration exits via an exception ([basic.start.init], paragraph 6)
  7. When destruction of an object with static or thread storage duration exits via an exception ([basic.start.term], paragraph 1)
  8. When execution of a function registered with std::atexit()or std::at_quick_exit() exits via an exception ([support.start.term], paragraphs 8 and 12)
  9. When the implementation’s default unexpected exception handler is called ([except.unexpected], paragraph 2)
    Note that std::unexpected() is currently deprecated.
  10. When std::unexpected() throws an exception that is not allowed by the previously violated dynamic-exception-specification, and std::bad_exception() is not included in that dynamic-exception-specification ([except.unexpected], paragraph 3)
  11. When the function std::nested_exception::rethrow_nested() is called for an object that has captured no exception ([except.nested], paragraph 4)
  12. When execution of the initial function of a thread exits via an exception ([thread.thread.constr], paragraph 5)
  13. When the destructor is invoked on an object of type std::thread that refers to a joinable thread ([thread.thread.destr], paragraph 1)
  14. When the copy assignment operator is invoked on an object of type std::thread that refers to a joinable thread ([thread.thread.assign], paragraph 1)
  15. When calling condition_variable::wait()condition_variable::wait_until(), or condition_variable::wait_for() results in a failure to meet the postcondition: lock.owns_lock() == true or lock.mutex() is not locked by the calling thread ([thread.condition.condvar], paragraphs 11, 16, 21, 28, 33, and 40)
  16. When calling condition_variable_any::wait()condition_variable_any::wait_until(), or condition_variable_any::wait_for() results in a failure to meet the postcondition: lock is not locked by the calling thread ([thread.condition.condvarany], paragraphs 11, 16, and 22)

In many circumstances, the call stack will not be unwound in response to an implicit call to std::terminate(), and in a few cases, it is implementation-defined whether or not stack unwinding will occur. The C++ Standard, [except.terminate], paragraph 2 [ISO/IEC 14882-2014], in part, states the following:

In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called. In the situation where the search for a handler encounters the outermost block of a function with a noexcept-specification that does not allow the exception, it is implementation-defined whether the stack is unwound, unwound partially, or not unwound at all before std::terminate() is called. In all other situations, the stack shall not be unwound before std::terminate() is called.

Do not explicitly or implicitly call std::quick_exit(),  std::abort(), or std::_Exit(). When the default terminate_handler is installed or the current terminate_handler responds by calling std::abort() or std::_Exit(), do not explicitly or implicitly call std::terminate()Abnormal process termination is the typical vector for denial-of-service attacks.

The std::exit() function is more complex. The C++ Standard, [basic.start.main], paragraph 4, states:

Terminating the program without leaving the current block (e.g., by calling the function std::exit(int) (17.5)) does not destroy any objects with automatic storage duration (11.4.6). 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.

You may call std::exit() only in a program that has not yet initialized any objects with automatic storage duration.

Noncompliant Code Example

In this noncompliant code example, the call to f(), which was registered as an exit handler with std::at_exit(), may result in a call to std::terminate() because throwing_func() may throw an exception.

#include <cstdlib>
 
void throwing_func() noexcept(false);
 
void f() { // Not invoked by the program except as an exit handler.
  throwing_func();
}
 
int main() {
  if (0 != std::atexit(f)) {
    // Handle error
  }
  // ...
}

Compliant Solution

In this compliant solution, f() handles all exceptions thrown by throwing_func() and does not rethrow.

#include <cstdlib>

void throwing_func() noexcept(false);

void f() { // Not invoked by the program except as an exit handler.
  try {
    throwing_func();
  } catch (...) {
    // Handle error
  }
}

int main() {
  if (0 != std::atexit(f)) {
    // Handle error
  }
  // ...
}

Exceptions

ERR50-CPP-EX1: It is acceptable, after indicating the nature of the problem to the operator, to explicitly call std::abort()std::_Exit(), or std::terminate() in response to a critical program error for which no recovery is possible, as in this example.

#include <exception>

void report(const char *msg) noexcept;
[[noreturn]] void fast_fail(const char *msg) {
  // Report error message to operator
  report(msg);
 
  // Terminate
  std::terminate();
}
 
void critical_function_that_fails() noexcept(false);
 
void f() {
  try {
    critical_function_that_fails();
  } catch (...) {
    fast_fail("Critical function failure");
  }
}

The assert() macro is permissible under this exception because failed assertions will notify the operator on the standard error stream in an implementation-defined manner before calling std::abort().

Risk Assessment

Allowing the application to abnormally terminate can lead to resources not being freed, closed, and so on. It is frequently a vector for denial-of-service attacks.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

ERR50-CPP

Low

Probable

Medium

P4

L3

Automated Detection

Tool

Version

Checker

Description

Astrée

22.10

stdlib-use
Partially checked
CodeSonar
8.1p0

BADFUNC.ABORT
BADFUNC.EXIT

Use of abort
Use of exit

Helix QAC

2024.1

C++5014
Klocwork
2024.1
MISRA.TERMINATE
CERT.ERR.ABRUPT_TERM

LDRA tool suite
9.7.1

 

122 S

Enhanced Enforcement

Parasoft C/C++test

2023.1

CERT_CPP-ERR50-a
CERT_CPP-ERR50-b
CERT_CPP-ERR50-c
CERT_CPP-ERR50-d
CERT_CPP-ERR50-e
CERT_CPP-ERR50-f
CERT_CPP-ERR50-g
CERT_CPP-ERR50-h
CERT_CPP-ERR50-i
CERT_CPP-ERR50-j
CERT_CPP-ERR50-k
CERT_CPP-ERR50-l
CERT_CPP-ERR50-m
CERT_CPP-ERR50-n

The execution of a function registered with 'std::atexit()' or 'std::at_quick_exit()' should not exit via an exception
Never allow an exception to be thrown from a destructor, deallocation, and swap
Do not throw from within destructor
There should be at least one exception handler to catch all otherwise unhandled exceptions
An empty throw (throw;) shall only be used in the compound-statement of a catch handler
Exceptions shall be raised only after start-up and before termination of the program
Each exception explicitly thrown in the code shall have a handler of a compatible type in all call paths that could lead to that point
Where a function's declaration includes an exception-specification, the function shall only be capable of throwing exceptions of the indicated type(s)
Function called in global or namespace scope shall not throw unhandled exceptions
Always catch exceptions
Properly define exit handlers
The 'abort()' function from the 'stdlib.h' or 'cstdlib' library shall not be used
Avoid throwing exceptions from functions that are declared not to throw
The 'quick_exit()' and '_Exit()' functions from the 'stdlib.h' or 'cstdlib' library shall not be used

Polyspace Bug Finder

R2023b

CERT C++: ERR50-CPPChecks for implicit call to terminate() function (rule partially covered)
PVS-Studio

7.30

V667, V2014
RuleChecker
22.10
stdlib-use
Partially checked
SonarQube C/C++ Plugin
4.10
S990

Related Vulnerabilities

Search for other vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

Bibliography

[ISO/IEC 9899-2011]Subclause 7.20.4.1, "The abort Function"
Subclause 7.20.4.4, "The _Exit Function"
[ISO/IEC 14882-2014]

Subclause 15.5.1, "The std::terminate() Function"
Subclause 18.5, "Start and Termination" 

[MISRA 2008]Rule 15-3-2 (Advisory)
Rule 15-3-4 (Required)



18 Comments

  1. Doesn't this need an exception for if you call fork and you need to exit the child process, which hasn't successfully executed an exec function?

    1. First, fork() is POSIX not ISO C++, so it is out-of-scope of this rule :) And this fact does interfere with satisfactorily answering your question.

      Depending on your platforms fork() implementation, it may indeed be safe to terminate a forked child process that is executing the same code, but has no destructors to invoke. But I can't say for sure without official POSIX diocumentation about how fork() works with C++ destructors.

      1. #include <sys/wait.h>
        #include <unistd.h>
        #include <iostream>
        
        int main() {
            std::cout << "Before fork";
            switch(fork()) {
            case -1:
                std::cerr << "Fork failed\n";
                return 1;
            case 0:
                std::exit(0);
            default:
                std::cout << "\nIn parent\n";
                wait(nullptr);
            }
        }

        That prints:

        Before fork
        In parent
        Before fork

        Probably not what's wanted. If you use std::_Exit instead of std::exit, then Before fork is only printed once, as desired. (Oh, and if this used vfork() instead of fork(), then exit rather than _Exit would be UB.)

        1. That is weird...I would expect that fork() should have dropped you off in the switch statement, and not re-executed the "Before fork" statement.

          I thought that calling fork() inside switch might explain the behavior, but it persists even if we hoist the fork() outside the switch. This problem also exists in C. And gcc vs clang doesn't matter.

          POSIX IEEE Std 1003.1-2017 says wrt fork():
          Both processes shall continue to execute from the fork() function.

        2. I figured out what is happening. Your C++ example works as expected if you flush the output:

          ...
          std::cout << "Before fork" ;
          std::cout.flush();
          switch(fork()) {
          ...

          My skim of fork(3) suggests that unflushed buffered data can get duplicated.

          Sigh. This is what happens when I try a coding puzzle at 4pm Friday :/

          1. This is indeed what's going on, but your workaround kind of misses the point. Just like it's important to not skip cleanup/flushes, it's also important to not run them early/twice, and there's not really a feasible way to ensure the latter in a forked child other than _Exit, since in a real program, you don't know every single buffer there is to flush ahead of time, and in some cases, even if you did there's nothing you can do about it.

            Consider this scenario: a constructor creates a temporary file, and the corresponding destructor deletes it. The constructor runs, the program forks, and the destructor runs in the child. Now the file is gone, but the parent still needs it.

            1. That's the problem with fork()...it shares everything whether the child process needs it or not.

              I think there should be a POSIX recommendation or rule saying "do not let unbuffered data persist when forking a child process, if both parent and child subsequently perform read or write on that file descriptor", as the results are almost never what the developer wanted. That rule would address your example of a temporary file being created in a parent process and destroyed in a child process.

              1. Such a recommendation/rule wouldn't be practical to follow in non-toy programs, since libraries often do that and don't give you a way to release anything without breaking what they're doing. (Side note: I'm also concerned that this rule itself is getting impractical to follow, now that for most programs it basically means "never call exit".)

                1. WRT my fork recommendation, it would be a rule in the C POSIX standard. There is no C++/POSIX standard and we don't have the funds to create a good one. I personally have no idea how destructors and fork() interact...if a fork() call means destructors get called both in the parent & child threads, then you probably should never call fork() in a C++ program.

                2. This rule is trying to comply with RAII...to make sure all destructors are called on normal termination (at the end of main(). That is, if a dtor was not invoked then by definition it was not a 'normal' exit :). Perhaps this rule now needs an exception saying "it is ok to call abort() or terminate() if your program detects a security flaw such that worse things than a crash can result if the program does not terminate immediately".

                  1. I like this article by Raymond Chen: https://devblogs.microsoft.com/oldnewthing/20120105-00/?p=8683

                    The building is being demolished. Don’t bother sweeping the floor and emptying the trash cans and erasing the whiteboards. And don’t line up at the exit to the building so everybody can move their in/out magnet to out. All you’re doing is making the demolition team wait for you to finish these pointless housecleaning tasks. Okay, if you have internal file buffers, you can write them out to the file handle. That’s like remembering to take the last pieces of mail from the mailroom out to the mailbox. But don’t bother closing the handle or freeing the buffer, in the same way you shouldn’t bother updating the “mail last picked up on” sign or resetting the flags on all the mailboxes.

                    IMO, if something that a destructor does matters even if the program is exiting (like flushing an output buffer), then the program/library should register an atexit handler to make sure it gets done, but for most destructors, they're just deallocating memory that's about to be deallocated anyway, so I don't see harm in skipping them.

              2. FWIW, when working on the C++ guidelines, I explicitly elected to not cover POSIX. POSIX is a superset of the C standard and is designed such that a conflict between it and C means that C wins. But there's not as clear of a relationship to the C++ standard and we didn't have the resources to consider how POSIX and C++ interact because there's a whole lot of surface area to cover there (things like std::filesystem, std::thread, etc). I'd be wary of adding a POSIX section to the CERT C++ standard without loud warnings about its completeness.

                1. I'm not overly concerned that we don't have rules for POSIX. What I am concerned about is that a lot of our rules for standard C++ make it impossible to correctly use a lot of POSIX functionality.

                  1. I guess I see that as two sides of the same coin. We didn't consider POSIX at all, so the rules are likely harder to use in conjunction with POSIX functionality and there are no rules specifically about POSIX.

                    FWIW, I don't think that's as bad of a situation as it would be in C – C++ has a lot of improved replacement facilities for POSIX functionality and so it being hard to use some parts of POSIX may not be a bad thing (like pthreads and file system stuff).

  2. These sentences seem misleading:

    The std::abort(), std::quick_exit(), and std::_Exit() functions are used to terminate the program in an immediate fashion. They do so without calling exit handlers registered with std::atexit() and without executing destructors for objects with automatic, thread, or static storage duration.


    It is acceptable to call a termination function that safely executes destructors and properly cleans up resources, such as std::exit().

    The problem is that they make it sound like std::exit() calls destructors for objects with automatic storage duration, but it actually doesn't:

    #include <iostream>
    
    struct foo {
        std::string s;
        foo(std::string s) : s(s) {
            std::cout << "Constructing " << s << std::endl;
        }
        ~foo() {
            std::cout << "Destructing " << s << std::endl;
        }
    };
    
    foo static_foo{"static_foo"};
    
    int main() {
        foo automatic_foo{"automatic_foo"};
        std::exit(0);
    }

    Result:

    Constructing static_foo
    Constructing automatic_foo
    Destructing static_foo
    1. Agreed. I've clarified when it is OK to call std::exit().

      1. But it doesn't just skip the current function's variables: it skips all of them. This program doesn't call automatic_foo's destructor either, even though your new wording is fine with it:

        #include <iostream>
        
        struct foo {
            std::string s;
            foo(std::string s) : s(s) {
                std::cout << "Constructing " << s << std::endl;
            }
            ~foo() {
                std::cout << "Destructing " << s << std::endl;
            }
        };
        
        foo static_foo{"static_foo"};
        
        void f() {
            // this function has not yet initialized any objects with automatic storage duration
            std::exit(0);
        }
        
        int main() {
            foo automatic_foo{"automatic_foo"};
            f();
        }
        1. Agreed. I s/function/program/ in the normative text about exit(().