Using std::terminate

In this article we are going to see what happens when your program calls std::terminate. If you think it simply terminates the program, you may find it interesting that it is not always so, and the function may be used to do a couple of other useful things, that will make your program better.

What is std::terminate for?

For one, its goal is not simply to terminate the program. True, it calls std::abort by default, if you do not want it to do anything else, but its general goal is somewhat different. Technically speaking, std::terminate calls one of the installed terminate-handlers; not necessarily the most recently installed one. “Terminate-handler” is a function registered with std::set_terminate. Technically, compiler will let you register any function as a terminate-handler if it matches the signature void(void), but there are some further semantic requirements that terminate-handlers need to meet in order to be effectively used in your program. These restrictions will become natural when we understand the design philosophy behind error handling mechanism in C++.

While term “error” is really ambiguous in programming we will still use it in this article to differentiate two kinds of situations: recoverable and irrecoverable errors. Our program may face different adverse run-time situations. Depending on their nature it may be possible to still continue running the program after doing some additional work, or getting some help from the user, but in other cases no recovery may be possible. The first category contains situations where we suddenly loose Internet connection. Then switching to another network adapter, or asking the human user to plug the cable back in may be enough to recover to normal program operations. Another example could be a service that receives a request but due to insufficient resources or incorrect input it cannot process the request. A possible recovery from such situation is to abandon the request, and proceed to the next one, in hope that it will be more successful. The second category contains situations like program bugs, where incorrectly written program causes writes to protected memory, thus causing a crash or random results. A second example of irrecoverable situation is when too many otherwise recoverable errors coincides, making it impossible for the recovery procedure to succeed.

In the spirit of the above distinction the process of stack unwinding is design to handle recoverable errors. Some of program actions are abandoned, only necessary clean-up is performed; all that in hope that it will be possible to recover from the error, by handling the active exception, and proceed to the normal program flow. In contrast, std::terminate is designed to handle the the irrecoverable errors. In this case the program is not supposed to resume its normal operation, so our options inside std::terminate are limited. Yet, we have some choice.

While the process of stack unwinding is platform-independent (C++ standard specifies exactly how the program should behave regardless of the platform or compiler), the second level of error handling — std::terminate — is very platform-specific. Therefore C++ standard often uses terms “implementation-defined behavior” and “undefined behavior” when it describes the behavior of std::terminate. The difference between the two notions is that in case of the former, the implementation (compiler) must define what happens in such situation and say it clear in the compiler documentation, whereas in the case of the latter the implementation might specify the behavior, but may as well not. Thus using effectively the feature of std::terminate requires detailed familiarity of the platform your program will be compiled and run on.

What can you (not) do in termination handler?

Definitely, you cannot return from your handler, the way normal functions return. std::terminate is declared as [[noreturn]], which means that an attempt to return from the function triggers undefined behavior, which in many platforms is implemented as the call to std::abort. So typically in the termination handler you want to do something and then call std::abort or std::_Exit. Before we describe what “something” means we will try to focus on stopping the program at the end of the handler.

Note the new function std::_Exit. It is very similar to std::abort: it halts the program immediately without calling any clean-up functions (on some implementations it might flush and close open streams); the only difference is that with std::_Exit you can choose what value is returned to program’s environment. It is very different from function std::exit. The latter triggers a call to all clean-up functions: destructors of objects with static storage (globals, class statics, function-local statics) and functions registered with std::atexit. This poses a risk that in case std::terminate is called as a consequence of throwing exception from global destructor (say, the very last global destructor), calling std::exit would launch the process of calling all destructors again… Therefore, C++ Standard defines the situation of calling std::exit after the clean-up of globals has started, an undefined behavior.

Can you throw an exception out of std::terminate? There is no good answer for that. C++ Standard says “Required behavior: A terminate_handler shall terminate execution of the program without returning to the caller.” This is not really clear. However, note that in C++11 std::terminate is declared as noexcept which means that if you throw from it, std::terminate is called — recursively.

So, given that we will terminate the execution of the program ultimately, what can we do before that happens? First, we must be aware that std::terminate might have been called before globals or thread-locals or class-statics have been initialized (due to a throw from global initialization), or after they have been destroyed (due to a throw from destructor or clean-up function), therefore we cannot rely on any globals. The only exception are eight STD globals: cin, cout, cerr, clog, win, wout, werr and wlog. They are guaranteed to be never destroyed. Apart from that, the standard does not specify how long it should take you to terminate the program, so technically, you are allowed to re-run the program in termination handler, like this:

void runProgram();

int main() {
    std::set_terminate( &runProgram );
    // if std::terminate is called run the second time
    runProgram(); // run normally    
}

Noöne would really recommend writing programs like this, if not for any other reason, calling std::terminate recursively would sooner or later cause the stack overflow. But it also looks a bit insane. It is just to show the possibilities. One similar, and suspicious, thing to do is to pause this thread of execution for arbitrary long period, in order for other threads to finish their tasks before the program is terminated.

Second level of error handling

Now, to practical things that you can do in termination handler. We already know that we cannot resume the program. First, we can release critical system resources. This is different than just releasing resources like memory. Typically the system will clean up things after the program when it dies. It will release all memory. It will likely flush unwritten streams. It will likely release all system threads. Locks on mutexes will no longer matter. Yet, there may be some global system (rather than program) resources that may get hung if the program terminates abruptly, affecting other programs in the system. If this is the case for your program, you should release any such resource in a safe way. That is, bare in mind that std::terminate may be called before you initialize globals (your resource does not need to be released then) or after you have destroyed some globals (your resource has already been released, and you would be risking releasing it twice). Typical procedures for making sure that something needs to be called exactly once may not work, because they usually rely on globals (or similar), which would be too risky in std::terminate.

Second, you can simply log the event of calling std::terminate, but be aware of what tool you use for logging. Usually logging libraries use some globals, and globals are not generally reliable in std::terminate. The only globals you can safely rely on are cin, cout, cerr, clog, win, wout, werr and wlog — they will not be destroyed in the program. Also you may use some system-wide logger that is not program-local, and will be available even after the program terminates. If you need to log using logging library, you need to apply some patterns to make sure your global logging object will be alive in std::terminate. Here is one example.

Logger * logger = new Logger;

[[noreturn]] void onTerminate() noexcept {
    try{
        if( logger ) {
            logger->logAbnormalTermination();  
        }  
    }
    catch(...) {
        // ignore!
    }
    std::_Exit( EXIT_FAILURE );
}

int main() {
    std::set_terminate( &onTerminate );
    // run...
}

This code violates two well established practices in C++. First, we swallow any exception that is thrown while logging. Swallowing exceptions is usually a bad thing to do, because it conceals the information about the failure from the rest of the program. Even inside destructors this practice is at least controversial. (This is two different things: not throwing exceptions from destructors, and throwing and later concealing the exception.) However, the situation is different inside std::terminate: exception handling mechanism already failed and yielded to the second layer of error handling.

Second, we allocate memory for the logger and initialize it, but never bother to destroy it or release memory. This is a memory leak, and perhaps other resource leak if Logger acquires any resources. But memory leak at shut-down is not really a problem. Program will close in a moment and all memory will be released anyway. Other resources leak in Logger might be a problem, so you should make sure that your logger’s destruction can be safely skipped — this is again very platform- or logger-specific. But by never destroying the logger we guarantee that it will be working during termination.

Globals are initialized in two stages. First, they are all set to zero; our logger pointer will initially be null. Then, in the second pass of initialization, expression new Logger will be called. If it succeeds, our pointer will point to heap-allocated logger object; otherwise it will remain null. Thus the value of the pointer indicates if the logger has been initialized or not. (Remember that std::terminate might be called before the initialization of logger takes place, or as the result of the initialization of the logger.) So the if in termination handler checks if we had a chance to successfully initialize the logger; if not, then obviously no logging can occur.

If you need to know if std::terminate was called because someöne threw an exception that could not be handled, or for some different reason (see this post to see all possible reasons for calling std::terminate) you can use function std::current_exception. This technique has been suggested by Daniel Krügler here. In some aspects termination handler is also an exception handler:

[[noreturn]] void onTerminate() noexcept
{
    if( auto exc = std::current_exception() ) { 
        // we have an exception
        try{
            rethrow_exception( exc ); // throw to recognize the type
        }
        catch( MyException const& exc ) {
            // additional action
        }
        catch( MyOtherException const& exc ) {
            // additional action
        }
        catch( std::exception const& exc ) {
            // additional action
        }
        catch( ... ) {
            // additional action
        }
    }

    std::_Exit( EXIT_FAILURE );
}

Somewhat better than logging, you can call a system function to generate program memory dump (a core dump). And this is one of the reasons for sometimes surprising behavior in C++ that if an exception is thrown out of main and no compatible handler found, run-time is allowed not to unwind the stack. If the stack is not unwound (and “stack for unwinding” is usually connected to program stack) we can generate the dump exactly at the moment of throwing an unfortunate exception. Owing to that it will contain more information than if it was called after unwinding.

Last, if your application must run all the time, and there is noöne to restart it, e.g. in an embedded or real-time system, you can call a system function that would request the restart of the application, or the restart of the system.

Multi-threaded environments

It is worth mentioning at this point that there is a single termination handler installed for all threads. That is, setting termination handler in one thread makes it visible in all other threads. Also, note that whatever std::terminate does, it may happen that it will be executed concurrently in multiple threads, if more than one thread requests the call to std::terminate. Therefore you should make sure with a mutex that some function that needs to be called only once will not be called multiple times. Also, because C++ Standard does not explicitly specify otherwise, it follows that std::terminate is not thread-safe, and it may be safe to use a mutex inside your handler to prevent data races.

Installing termination handler

We already know that what std::terminate does is simply calling a termination handler. But what may be surprising is that it is not necessarily the most recently installed handler. The standard reads “If terminate is called directly by the program, the terminate handler is the one most recently set by a call to set_terminate. If terminate is called for any of several other reasons during evaluation of a throw expression, the terminate handler is the one in effect immediately after evaluating the throw expression.” This wording is a bit ambiguous. There are situations where std::terminate is not called directly by the program, but also there is no “throw expression” causing the termination. In order to avoid surprises it is better that you install your desired handler as soon as possible in the program and not change it afterwards. “As soon as possible” means before before main starts. You can use the following idiom for that.

const auto installed{ std::set_terminate(&handler) };

int main() {
    // run...
}

The value of global installed is not needed, but we need a global, because only the functions that are used during the initialization of globals can be called before main starts. This is not a perfect solution, because std::terminate may be called as the result of initialization of other globals that happen before our installation of the handler.

The fear for termination

For the end, I would like to address, often exaggerated in my opinion, concerns that a program could call std::terminate. You can sometimes see (or hear) programmers say “I prefer error codes because exceptions can terminate the program.” or “I cannot afford using exceptions because my program must not terminate, no matter what.”

The two-level mechanism of error handling is designed in such a way that std::terminate is called only when program will not be able to recover from error condition. If you do not want terminating the program focus on preventing the situations where program is corrupt beyond recovery. Do not perform non-trivial initialization for globals. Avoid writing any code in destructors (let them only call sub-objects’ destructors). Avoid optimistically specifying your functions with noexcept. Avoid std::threads (use std::async if possible).

It doesn’t reduce the risk of potential termination to zero, but there is no program in the world whose primary goal is to “run, no matter what.” Program has to do something useful. And if this is prevented by some exceptional circumstances, a program not doing anything useful (or doing something harmful) is not wanted by anyone.

In case of life-critical applications, where stopping the program could result in disaster, avoid acquiring resources at random times (resource acquisition is the primary source of exceptions). Relay on automatic-storage (stack) memory and avoid heap memory. If you are using STL containers that store only small number of elements, consider using stack allocators. Make sure that all resources you need are available before the program starts and are exclusively dedicated to the program. Avoid throwing exceptions for signalling any type of irregularity. Exceptions are good for signalling resource acquisition failure, but for things like input validation, other techniques like returning compound values (e.g., boost::optional) may be a more suitable choice. If the program still terminates, std::terminate can be used for making sure that it is restarted immediately. It still does not give you 100% certainty that the program will always work. If you require 100% certainty that the program will not fail, C++ may not me the best choice of the programming language. You should use a pure functional language, like Haskell, instead, where some properties of the program can be proven — in the mathematical sense.

This entry was posted in programming and tagged , , . Bookmark the permalink.

11 Responses to Using std::terminate

  1. Jon Kalb says:

    Andrzej,
    Very nice post.
    Regarding the leak of logger, while I don’t disagree that is it is harmless, but it may be flagged by static analyzers and it does offend my sensibilities because if “violates a well established practice in C++.” An alternative might look like this:

    Logger log_instance;
    Logger * logger = &log_instance;
    

    Here we have no leak and the address of logger is nil if and only if log_instance isn’t initialized. It addresses the issue with out violating the well established practice at the cost a static pointer.

    • Hi Jon,
      Thank you for your comment. It consists of the criticism of the proposed logging solution and the alternative. Let me address it LIFO order.

      The question I am trying to answer is: will the logging mechanism work correctly if std::terminate is called after function main has finished and we are in the middle of destroying global objects? (Some of them having been destroyed already). In other words, what happens if you want to use a logger while destroying some global object or calling some function registered with std::atexit, but the destructor of global object log_instance has already been (successfully) executed?

      I believe that such situation might happen, given that the LIFO order of destroying global objects only applies within a translation unit, and not necessarily across multiple TUs. I guess in that case the pointer would be pointing at the already destroyed object and calling it would be a UB.

      About the criticism of the solution with heap-allocated logger. I agree with your both statements: yes, it does violate a well established practice in C++. And yes, it will almost certainly be “flagged” by static (or even “dynamic” or “run-time”) analyzers, especially that sometimes even releasing resources in global destructors is reported as resource leak on some analyzers (it happens for me on VS 2005).

      Regarding the violation of a well established practice, my reasoning here is that “best practices” are a recommendation rather than something programmers should be forced to. They are good advice in most (say 95% of the cases) but at in some unusual enough cases every good practice falls short. I believe that here we have to deal with this 5% case, and face an uncomfortable trade-off between correctness and a good “best practice”.

      Regarding confusing the analyzers, my response is similar. Whatever the definition of a memory leak is, the heap-allocated logger is definitely one. But again, this special need of being able to safely log the destruction of global objects appears (IMO) more important than taking care of this memory leak. I observe (and this observation surprised me at first) that not every memory leak is harmful (although probably 99% of them are). If you detect memory leaks with static analyzer, its job is to report every memory leak. But your job, as the programmer, is not to avoid every memory leak: only the 99% of the harmful leaks.

      Of course, if there was a solution that is both safe and does not use a ‘controlled’ memory leak in implementation, such solution would be my preference also. It is just that I believe that the alternative has this potential of entering a UB.

    • Todd Greer says:

      There is an issue with Jon Kalb’s suggested approach to initializing logger. The intent is that logger will only be initialized if log_instance has been successfully initialized. This is not guaranteed, and is in fact not likely.

      I am assuming that these two lines are placed at namespace scope (so they have static storage duration), and that Logger’s default constructor does some sort of significant work (like opening a file or pipe). Reassembled, here’s the proposal I’m discussing:

      Logger log_instance;
      Logger * logger = &log_instance;
      
      [[noreturn]] void onTerminate() noexcept {
          try{
              if( logger ) {
                  logger->logAbnormalTermination(); 
              } 
          }
          catch(...) {
              // ignore!
          }
          std::_Exit( EXIT_FAILURE );
      }
      

      log_instance will first be zero-initialized as part of the program’s static initialization, and will later be dynamically initialized with its constructor.

      logger, on the other hand is initialized by a constant expression, and so is constant-initialized during static initialization. Since static initialization happens before dynamic initialization, logger will be initialized with the address of log_instance before log_instance is initialized.

      To avoid this problem, i would return to the original article’s approach (Logger* logger = new Logger;), and I would configure my leak checker to ignore that allocation. If that is to odious, i see two other alternatives:
      1. Make Logger’s constructor a constexpr constructor, so that log_instance can be constant-initialized during static initialization. This may be difficult.
      2. Replace logger’s initializer (&log-instance) with something that is not a constant expression. Here’s something that should work:

      Logger log_instance;
      Logger* logger = log_instance.logInitialization(), &log_instance;
      

      logger must now be dynamically initialized, since its initializer is not a constant expression. You have to be careful though, the compiler has considerable freedom to optimize by statically initializing if it can figure out how to do so. I think there are enough calls to I/O routines implied by all this logging that the optimizer can’t avoid doing this initialization dynamically, but it’s hard to be 100% sure. I think I’ll stick with the first approach.

      The requirements on initialization are in 3.6.2. If you’re going to take the last approach above, I strongly recommend reading all of 3.6.2 (including 3.6.2/3 which controls turning dynamic initialization into static). You may also want to review the as-if rule governing optimization in 1.9/1-8.

      Of course, if I’ve got anything wrong, please point it out.

      • Hi Todd. This is a very interesting and important observation. It looks like the solution proposed in the post works only by chance, because I did not put that much attention into the problem. I am replying immediately after having read your comment, so I didn’t verify if the optimizations you are concerned about are allowed (although it feel they might be), but I wonder if the following would be safer:

        Logger log_instance;
        Logger* logger = log_instance.logInitialization();
        

        And have logInitialization return the logger’s address.

        Regards,
        &rzej

        • Todd Greer says:

          Hi Andrzej. As long as Logger::logInitialization() does something that can’t be moved or skipped by the compiler as an optimization, it should solve the problem. Logging something should do thie trick, since that’s clearly part of the “observable behavior” of the program. Just put some loud comments in place describing what you’re doing to keep it from being undone in maintainence.

  2. Tony Van Eerd says:

    To initialize the logger, use inplace new.

    char memoryForLogger[sizeof(Logger)];
    Logger * logger = new (memoryForLogger) Logger;

    Then you have no allocation, no memory checker annoyances, and can still use logger as a flag (although not a thread-safe one. To be thread safe requires an atomic and a spin lock, etc).

    Might need a few more things, it has been a while since I needed to do this, but that was how we did it.

  3. Anatoliy says:

    Can I simply call std::terminate at end of onTerminate instead of std::_Exit?

    • No. We are installing onTerminate as “terminate handler”:

      std::set_terminate( &onTerminate );
      

      This means that each time someone calls std::terminate(), function onTerminate gets executed. If onTerminate called std::terminate internally, we would get an infinite recursion.

  4. Walking Dead says:

    Hello from 2016 🙂
    thx Todd.
    this solution (mentioned above) for “global memory leaks”

    char memoryForLogger[sizeof(Logger)];
    Logger * logger = new (memoryForLogger) Logger;
    …..
    Was this solution of a year? what a beauty
    and +1 for using placement new

  5. whoah this blog is wonderful i love reading your posts. Keep up the great work! You know, lots of people are searching around for this info, you can aid them greatly.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.