For those familiar with languages like Java, and C#, something like NullPointerException shouldn’t come as a surprise. But what about C++? C++ also has exceptions, right?
In C++ reading or writing at address zero is an access violation. By default an access violation will result in the immediate termination of the program. What else results in immediate termination of the program? Division by zero! There is no ArithmeticException, only a swift termination!
The OS’ SDK usually provides a way to catch such access violations and recover from them. This way of catching access violations involves a C callback method and a bit of setup.
Wouldn’t be nice if the setup would be one line of code and the C callback function would throw C++ exceptions behind the scenes?
But it does work like this. At least on Windows and Linux (I don’t have access to a macOS machine), and only with a few select compilers.
Before going further into details I would like to present my test case: define functions which do:
- Division by zero
- Reading from nullptr
- Writing at nullptr
- Write to an empy vector with the subscript operator []
- Read from an uninitialized shared_ptr
Execute them ten times to make sure that this is not only one time “wonder”. Every try
block will
have an instance of a RAII Message
object to make sure that stack unwinding is taking place, and
that we won’t have any resource leaks.
Test code
The test code is below:
The output of the program should be like this:
For brevity I displayed only the first block.
How should except::register_for_os_exceptions()
look like? Can it be done in a cross-platform way,
or only with platform specific code?
std::signal
std::signal
is part of the C library and subsequently also from C++ library. The cppreference.com
page has some information about this, but the example they provide doesn’t actually help with my
task at hand.
std::signal
should not be used in multi threading programs and it doesn’t provide additional
information about the error. For example for the SIGSEGV
signal we cannot get the address at which
the access violation has occurred.
This is what Rosetta Code has chosen for their C++ division by zero sample.
From the tests I have made I can say that the signal handling and recovery is not cross platform. It is at most one shot and only Visual C++ generates code that recovers.
Implementation of except::register_for_os_exceptions()
looks like this:
In the next part I would name std::signal as POSIX_SIGNAL.
Windows’ Structured Exception Handling (SEH)
Wikipedia describes Structured Exception Handling like this:
Microsoft Structured Exception Handling is the native exception handling mechanism for Windows and a forerunner technology to Vectored Exception Handling (VEH). It features the finally mechanism not present in standard С++ exceptions (but present in most imperative languages introduced later). SEH is set up and handled separately for each thread of execution.
The Microsoft implementation of SEH is based on a patent licensed from Borland, U.S. Patent 5,628,016. Open-source operating systems have resisted adopting a SEH-based mechanism due to this patent.
Microsoft supports SEH as a programming technique at the compiler level only. MS Visual C++ compiler features three non-standard keywords: __try, __except and __finally — for this purpose.
Those __try
, __except
, __finally
keywords look very scary. Luckily we don’t need to worry
about them. Microsoft provided the function set_se_translator()
which handles the C structured exceptions as C++ typed exceptions.
Implementation of except::register_for_os_exceptions()
looks like this:
As you can see now we can have null_pointer_exception
and division_by_zero_exception
because
SEH provides enough information.
The above code only works when the compiler parameter /EHa is set.
MSDN says about /EHa
the following:
The exception-handling model that catches both asynchronous (structured) and synchronous (C++) exceptions.
The /EHa compiler option is used to support asynchronous structured exception handling (SEH) with the native C++ catch(...) clause.
If you use /EHa, the image may be larger and might perform less well because the compiler does not optimize a try block as aggressively. It also leaves in exception filters that automatically call the destructors of all local objects even if the compiler does not see any code that can throw a C++ exception. This enables safe stack unwinding for asynchronous exceptions as well as for C++ exceptions.
Visual C++ obviously has support for SEH exceptions. But what about the clang-cl
drop in replacement?
Clang 4.0 documentation states the following about SEH:
Asynchronous Exceptions (SEH): Partial. Structured exceptions (__try / __except / __finally) mostly work on x86 and x64. LLVM does not model asynchronous exceptions, so it is currently impossible to catch an asynchronous exception generated in the same frame as the catching __try.
What about GCC on Windows (MinGW)? GCC has a Wiki page which states:
Unfortunately, GCC does not support SEH yet. Casper Hornstrup had created an initial implementation, but it was never merged into mainline GCC. Some people have expressed concerns over a Borland patent on SEH, but Borland seems to dismiss these concerns as balderdash.
In practice MinGW GCC 6.1.0 has the <eh.h>
header, but the linker gives an error:
undefined reference to '__imp__Z18_set_se_translatorPFvjP19_EXCEPTION_POINTERSE'
.
But what about Clang with Microsoft CodeGen which is available since Visual C++ 2015 Update 1?
Compilation gives an error: error : Element <ExceptionHandling> has an invalid value of "Async"
.
POSIX’s sigaction
POSIX had an update to std::signal
which works in multi-threaded environment and it provides
information about error cases, this update is sigaction.
Implementation of except::register_for_os_exceptions()
looks like this:
The above code works with the compiler flag -fnon-call-exceptions
.
Testing
I have put the code on github and I have tested on two machines: Lenovo W510 i7 laptop and a Raspberry Pi 2. For both machines I tested Windows 10, and Linux operating systems.
For Lenovo W510 i7:
- Windows 10 Visual C++ 2015 Update 3 SEH and POSIX_SIGNAL
- Windows 10 Visual C++ Clang 3.8 with Microsoft CodeGen SEH
- Windows 10 MSYS2 GCC 6.1.0, Clang 3.8.0 POSIX_SIGNAL
- Windows 10 Cygwin GCC 5.3.0, Clang 3.7.1 POSIX_SIGNAL and POSIX_SIGACTION
- Windows 10 Clang 3.9.0 with clang-cl SEH and POSIX_SIGNAL
- Windows 10 Ubuntu Bash (14.04) for Windows GCC 4.8.4 and Clang 3.5.0 POSIX_SIGNAL and POSIX_SIGACTION
- Windows 10 Ubuntu 14.04 in VirtualBox GCC 4.8.4 and Clang 3.5.0 POSIX_SIGNAL and POSIX_SIGACTION
- Kubuntu 16.04 GCC 5.4.0 and Clang 3.8.0 POSIX_SIGNAL and POSIX_SIGACTION
For Raspberry Pi 2:
- Raspbian Jessie GCC 4.9.2 and Clang 3.5.0 POSIX_SIGNAL and POSIX_SIGACTION
- Windows 10 IoT Visual C++ 2015 Update 3 SEH and POSIX_SIGNAL
In the reports below I have combined “readNullPointer” with “nullSharePointer” and “writeNullPointer” with “outOfBoundsVector”.
Windows 10
Compiler | Read nullptr | Write nullptr | / Zero |
---|---|---|---|
Visual C++ 2015 Update 3 SEH | YES | YES | YES |
Visual C++ 2015 Update 3 POSIX_SIGNAL | YES | YES | x |
Visual C++ Clang 3.8 with Microsoft CodeGen SEH | x | x | x |
MSYS2 GCC 6.1.0, POSIX_SIGNAL | x | x | x |
MSYS2 Clang 3.8.0 POSIX_SIGNAL | x | x | x |
Cygwin GCC 5.3.0, POSIX_SIGNAL | x | x | x |
Cygwin GCC 5.3.0, POSIX_SIGACTION | x | x | x |
Cygwin Clang 3.7.1 POSIX_SIGNAL | x | x | x |
Cygwin Clang 3.7.1 POSIX_SIGACTION | x | x | x |
Clang 3.9.0 with clang-cl SEH | x | x | x |
Clang 3.9.0 with clang-cl POSIX_SIGNAL | x | x | x |
Bash for Windows 10 GCC 4.8.4 POSIX_SIGNAL | x | x | x |
Bash for Windows 10 GCC 4.8.4 POSIX_SIGACTION | YES | YES | x |
Bash for Windows 10 Clang 3.5.0 POSIX_SIGNAL | x | x | x |
Bash for Windows 10 Clang 3.5.0 POSIX_SIGACTION | x | x | x |
Ubuntu 14.04 in VirtualBox GCC 4.8.4 POSIX_SIGNAL | x | x | x |
Ubuntu 14.04 in VirtualBox GCC 4.8.4 POSIX_SIGACTION | YES | YES | YES |
Ubuntu 14.04 in VirtualBox Clang 3.5.0 POSIX_SIGNAL | x | x | x |
Ubuntu 14.04 in VirtualBox Clang 3.5.0 POSIX_SIGACTION | x | x | x |
Visual C++ 2015 generates for POSIX_SIGNAL’s division by zero something else as it does for SEH. I might have found a compiler bug.
For Bash for Windows 10 and Ubuntu 14.04 in Virtual Box we have the same binary generated by GCC for POSIX SIGACTION. But on Bash for Windows 10 division by zero behaves like the binary which Visual C++ 2015 generates for POSIX_SIGNAL. It could be just a coincidence, or it may be the fact that Microsoft has reused their POSIX_SIGNAL implementation
Clang has a weird behavior for readNullPointer, it actually executes std::cout << *p << std::endl
code
(notice that 0, which on different platforms has different values):
Kubuntu 16.04
Compiler | Read nullptr | Write nullptr | / Zero |
---|---|---|---|
GCC 5.4.0 POSIX_SIGNAL | x | x | x |
GCC 5.4.0 POSIX_SIGACTION | YES | YES | YES |
Clang 3.8.0 POSIX_SIGNAL | x | x | x |
Clang 3.8.0 POSIX_SIGACTION | x | x | x |
By now I know that POSIX_SIGNAL is platform dependent, but I have no idea how to implement it to work with GCC on Linux.
Raspberry Pi Windows 10 IoT
Compiler | Read nullptr | Write nullptr | / Zero |
---|---|---|---|
Visual C++ 2015 Update 3 SEH | YES | YES | x |
Visual C++ 2015 Update 3 POSIX_SIGNAL | YES | YES | x |
The difference between Visual C++ x64 and ARM is that for SEH division by zero generates on ARM:
The destructor is not being called! I might have found another compiler bug.
Raspberry Pi Rasbpian
Compiler | Read nullptr | Write nullptr | / Zero |
---|---|---|---|
GCC 4.9.2 POSIX_SIGNAL | x | x | x |
GCC 4.9.2 POSIX_SIGACTION | x | x | x |
Clang 3.5.0 POSIX_SIGNAL | x | x | x |
Clang 3.5.0 POSIX_SIGACTION | x | x | x |
GCC on ARM doesn’t work with POSIX_SIGACTION as it does on Desktop. Could be another compiler bug.
Microsoft can generate for ARM code which works almost as on x64, I don’t see why GCC shouldn’t do the same.
You can find all the output of all programs on github.
Performance
We all know that exceptions are not loved by C++ developers. But nowadays with the advent of Zero Cost Exceptions there should not be a speed penalty for using them (in error cases only).
If
statements have a cost,
considerably smaller than the cost of throwing an exception. But if you have a lot of them at some
point the cost of all those ifs will be bigger than the cost of occasionally throwing an exception.
You can try out this benchmark (forked from Bogdan Vatră’s repository) to find out at which point exceptions are faster than return codes
The benchmark doesn’t use except
, but the performance with a division_by_zero_exception
should be in the same ballpark.
Binary compiled with Visual C++ 2015 Update 3 x64 performed on my Lenovo W510 i7 like this:
Conclusion
As you can see it is possible to handle OS exceptions is a cross platform way with the help of a very small library. It works on Windows with Visual C++ (x64, ARM) and on Linux with GCC (x64).