Divide by Zero Prevention: Traps, Exceptions, and Portability

Matthew Roever
Level Up Coding
Published in
6 min readMay 4, 2020

--

An examination of multiple solutions.

Black hole effect created using a spinning object emitting sparks.
Photo by Kamesh Vedula on Unsplash

I am working on creating a safe programming language. One of the major points I need to address while converting source code to assembly is how to handle integer division. With floating point division, dividing by zero is valid. The result may vary depending on your processor, but it is guaranteed to be either NaN, +INF, or -INF. Integer division is another story altogether. When performing integer division, the processor will trap when dividing by zero.

Trap: A processor level exception. Conceptually they are similar to software exceptions. When the input could not be processed an error was thrown*. However, there is a catch. They will probably kill your program since they cannot be directly interacted with (unless you’re the OS). *CPUs cannot actually throw an error; the OS is notified when a trap occurs.

Traps and Operating Systems:

The way traps are handled is very OS specific. On a POSIX (or related) operating system such as Unix or Linux a trap will result in a signal being raised by the OS. The registered signal handler is then responsible for dealing with the signal. Windows uses a different method which I will get to later.

Signal: A CPU messaging system used by the OS to communicate on and between multiple threads. By default, exception signals will terminate the program if a user defined handler does not handle it.

Back to POSIX, when a trap occurs the OS will select the appropriate signal defined by the POSIX standard. In the case of integer division by zero the signal is SIGFPE. Once the signal is set the OS will call the registered signal handler. When using C/C++ the default signal handler will terminate the program for exception signals. This is why C/C++ programs instantly blow up when division by zero happens.

While looking for a safe solution to redirect the signal to an exception I saw many proposed solutions create a custom signal handler that throws an exception when it intercepts a SIGFPE signal. THIS IS UNSAFE. The C/C++ standards explicitly defines a signal handler as being noexcept. If you throw in a signal handler your program is undefined, misbehaved, and probably going to crash.

Why is this the case? First of all, the thread answering the signal is not guaranteed to be the thread the signal occurred on. Signal handling is an interrupt process. Basically, the OS will suspend a running thread when a signal is raised (like hitting the pause button on whatever is running). The suspended thread is redirected to the signal handler to process the signal. The thread that is suspended might be the thread the signal occurred on, or not. Its random chance. Secondly, the signal handler has no stack. Since the handler is an interrupt it exists outside of the normal execution format. When throwing out of a signal handler you are unwinding nothing since there is no stack. Now I hear some saying “but this works on Windows”. Windows uses a different exception handling model, so ya… throwing in a signal handler might work. But that doesn’t change the fact that throwing out of signal handler is undefined on all operating systems. On Linux and most operating systems your throw is an armed bomb.

Stack: The data structure used to store information about active functions in your program; short term working memory. See Wikipedia for more details.

Unwinding: The process of removing data from the stack when an exception occurs. Unwinding will remove frames (data specific to a single function call) from the stack until a function with an exception handler that matches the exception is found.

What Makes Windows Special:

Part of the Windows exception handling model is Structured Exception Handling (SEH). SEH is a Windows specific extension to C designed to enable recovery from low-level exceptions, including traps. When using MSVC, SEH exceptions are labeled as “WIN32 Exceptions” in the exception breakpoint settings.

To catch an SEH exception directly we must use a Microsoft specific C extension. Rather than using a C++ try-catch statement we will use __try-__except. They function exactly the same way only this time we are catching the raw SEH exception. Since this is a C extension, proper unwinding does not naturally occur. To safely use SEH, the user must manually perform any necessary unwinding between the source of the exception and the catch. Ideally, SEH exception handling should only be used when the position where the exception can occur is known. This way memory leaks can be prevented, and in the case of my example below, no manual unwinding is necessary.

What’s great about Windows is that traps are reported using SEH exceptions rather than signals. This makes integer division by zero a SEH exception! Without further ado, lets divide by zero and survive:

Code for SEH exception handling when dividing by zero. See GitHub.
Figure 1: Catching a structured exception handling exception for integer divide by zero — By Author

The __except statement is noticeably more complex that a C++ catch statement. While catch statements automatically match based on the data type, with __except we must manually implement catch matching. For this example, I am looking or EXCEPTION_INT_DIVIDE_BY_ZERO, but every just about every hardware trap has an equivalent exception on Windows. The benefit of this method over using signal handling is that SEH error handling doesn’t interrupt a thread; it is no different than the usual try-catch. This allows us to throw out of the __except block. No undefined behavior. No armed bomb. Running this function with a 0 denominator results in Divide by zero. -seh_div being thrown. Perfect.

Implementing Safe Division:

After getting distracted by signals and error handling lets come back to the central goal: designing a method for safe division. Using SEH error handling is a valid way on Windows, but it’s not portable. The next question to answer is how this compares to simple checked division and unsafe (unchecked) division.

Code for checked and unchecked division. See GitHub.
Figure 2: Unsafe division and basic checked division — By Author

Though I want to create a safe language, performance is also important. In order to make an informed design decision let’s use a basic benchmark. Perform division 1 billion times while measuring how long it takes. I disabled all optimizations and inlining to make sure this is a fair test.

Code for function benchmark. See GitHub.
Figure 3: Benchmark used to test division formats — By Author
Results from benchmarks: unsafe 3.986 seconds, checked 4.321 seconds, SEH 4.332 seconds.
Figure 4: Benchmark results — By Author

Unsafe division was fastest which comes as no surprise. What is shocking, to me at least, is that checked division and SEH division required the same amount of time. I expected SEH division to be closer to unchecked division since the __except block is unused when division is valid. Based on this test, safe division takes about 10% longer than unsafe division across methods.

With these results I feel comfortable using the checked_div code as a substitute to all unchecked division. It has proven itself to have minimal overhead while also being platform independent. The Windows specific SEH method is interesting, but the lack of performance gain provides no reason to use an unportable method.

--

--

I am a Civil Engineer that codes. I write about compilers, computer graphics, and entrepreneurship.