What is the defined behavior of the following program, if any?
#include <iostream>
#include <exception>
#include <cstdlib>
void i_throw()
{
std::cout << "i_throw()" << std::endl;
// std::terminate() is noexcept so if the terminate handler throws...
// then the terminate handler is called...
// std::terminate is [[noreturn]] so don't return
try
{
throw 7;
}
catch(...)
{
std::cout << "caught exception, re-throw()-ing" << std::endl;
throw;
}
std::cout << "got here!" << std::endl;
std::abort();
}
int main()
{
std::set_terminate(i_throw);
throw;
std::terminate();
}
With gcc and clang I get the following output:
i_throw()
caught exception, re-throw()-ing
Aborted (core dumped)
Example edited after first few comments.
(I don't know why I have both throw;
and std::terminate();
. I don't want to change the example so just pretend only one of those two is there.)
std::terminate is what is automatically called in a C++ program when there is an unhandled exception. This is essentially the C++ equivalent to abort , assuming that you are reporting all your exceptional errors by means of throwing exceptions.
The uncaught_exception() function is most useful for preventing program termination due to a function that exits with an uncaught exception while another exception is still active.
The terminate function is used with C++ exception handling and is called in the following cases: A matching catch handler cannot be found for a thrown C++ exception. An exception is thrown by a destructor function during stack unwind. The stack is corrupted after throwing an exception.
Explanation: As the func() is throwing a const char* string but we the catch block is not catching any const char* exception i.e. exception thrown is not handled therefore the program results into Aborted(core dumped).
The above question can be boiled down to understanding the behavior of the following two code snippets.
Sample 1: throw with no active exception
int main()
{
try{
throw;
}catch(...){
std::cout<<"caught"<<endl; //we never reach here
}
return 0;
}
If you run the above code it crashes as below
terminate called without an active exception
Aborted (core dumped)
Sample 2: throw with active exception
int main()
{
try{
throw 7;
}catch(...){
std::cout<<"caught"<<endl; //will be caught
}
return 0;
}
Running it gives a predictable output
caught
If you generate the assembly of the code ( g++ -S option
). You'll notice the following cxx_abi calls for throw vs throw 7
throw;
gets converted to call __cxa_rethrow
and
throw 7;
gets converted to call __cxa_throw
Here's the code for __cxa_throw
extern "C" void
__cxxabiv1::__cxa_throw (void *obj, std::type_info *tinfo,
void (_GLIBCXX_CDTOR_CALLABI *dest) (void *))
{
PROBE2 (throw, obj, tinfo);
__cxa_eh_globals *globals = __cxa_get_globals ();
globals->uncaughtExceptions += 1;
// code removed for brevity
//.......
// Below code throws an exception to be caught by caller
#ifdef _GLIBCXX_SJLJ_EXCEPTIONS
_Unwind_SjLj_RaiseException (&header->exc.unwindHeader);
#else
_Unwind_RaiseException (&header->exc.unwindHeader);
#endif
// Some sort of unwinding error. Note that terminate is a handler.
__cxa_begin_catch (&header->exc.unwindHeader);
std::terminate ();
}
So, in the OP Code throw 7;
will be caught by corresponding catch(...)
and will be re-thrown by throw;
Here's the code for __cxa__rethrow
extern "C" void
__cxxabiv1::__cxa_rethrow ()
{
__cxa_eh_globals *globals = __cxa_get_globals ();
__cxa_exception *header = globals->caughtExceptions; // We are not re
globals->uncaughtExceptions += 1;
// Watch for luser rethrowing with no active exception.
if (header)
{
// Code removed for brevity
// .....
// Below code rethrows the exception
#ifdef _GLIBCXX_SJLJ_EXCEPTIONS
_Unwind_SjLj_Resume_or_Rethrow (&header->unwindHeader);
#else
#if defined(_LIBUNWIND_STD_ABI)
_Unwind_RaiseException (&header->unwindHeader);
#else
_Unwind_Resume_or_Rethrow (&header->unwindHeader);
#endif
#endif
}
std::terminate ();
}
In both the cases, we could see that std::terminate()
is not yet called from the __cxx_*
. After being thrown by above abi's we are at the following location in the code.
refer to the cxx_abi for terminate the code.
void
__cxxabiv1::__terminate (std::terminate_handler handler) throw ()
{
__try
{
handler (); // Our handler has thrown an int exception
std::abort ();
}
__catch(...) // Exception is caught here and process is aborted.
{ std::abort (); }
}
void
std::terminate () throw()
{
__terminate (get_terminate ());
}
Summary
As per my understanding, the re-throwing of the exception from the handler is resulting in catching the re-thrown exception in __cxxabiv1::__terminate
. Where it calls abort()
. Clearly, the std::terminate()
[from __cxa_rethrow] method didn't come into the picture, that's why the control never reached std::cout << "got here!" << std::endl;
Infinite Recursion
What happens if we changed the terminate_handler to the following:
void i_throw()
{
std::cout << "i_throw()" << std::endl;
throw;
std::cout << "got here!" << std::endl;
std::abort();
}
To understand this, we could look at __cxa_rethrow()
as mentioned above.
Since, there's no active exception that is being thrown, __cxa_rethrow()
would end-up calling std::terminate()
, thereby, causing infinite recursion.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With