Cristian Adam

NullPointerException in C++

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:

#include <iostream>
#include <sstream>
#include <vector>
#include <memory>
#include <map>
#include <functional>

#include <except/except.hpp>

struct Message
{
    std::string message;
    Message(const std::string& aMessage) : message(aMessage)
    {
        std::cout << "Message: " << message << std::endl;
    }
    
    ~Message()
    {
        std::cout << "~Message: " << message << std::endl;
    }
};

void readNullPointer()
{
    try
    {
       Message msg("read from nullptr");
       int* p = nullptr;
       std::cout << *p << std::endl;
    }
    catch (const std::exception& ex)
    {
        std::cout << ex.what() << std::endl;
    }
}

void writeNullPointer()
{
   try
   {
      Message msg("write to nullptr");
      int* p = nullptr;
      *p = 42;
      std::cout << *p << std::endl;
   }
   catch (const std::exception& ex)
   {
       std::cout << ex.what() << std::endl;
   }
}

void divisionByZero()
{
   try
   {
      Message msg("division by zero");
      int a = 42;
      volatile int b = 0;
      std::cout << a / b << std::endl;
   }
   catch (const std::exception& ex)
   {
       std::cout << ex.what() << std::endl;
   }
}

void outOfBoundsVector()
{
    try
    {
        Message("out of bounds vector");
        std::vector<int> v;
        v[0] = 42;
        std::cout << v[0] << std::endl;
    }
    catch (const std::exception& ex)
    {
        std::cout << ex.what() << std::endl;
    }
}

void nullSharedPointer()
{
    try
    {
        Message("reading empty shared_ptr");
        std::shared_ptr<int> sp = std::make_shared<int>(42);
        std::shared_ptr<int> sp2;
        sp.swap(sp2);
        
        std::cout << *sp << std::endl;
    }
    catch (const std::exception& ex)
    {
        std::cout << ex.what() << std::endl;
    }
}

std::vector<std::function<void()>> processArguments(int argc, char* argv[])
{
    std::vector<std::string> arguments(argv, argv + argc);

    std::map<std::string, std::function<void()>> functions
    {
        { "readNullPointer", readNullPointer },
        { "writeNullPointer", writeNullPointer },
        { "nullSharePointer", nullSharedPointer },
        { "outOfBoundsVector", outOfBoundsVector },
        { "divisionByZero", divisionByZero }
    };

    std::vector<std::function<void()>> callList;

    if (arguments.size() == 1)
    {
        std::ostringstream os;
        for (auto pair : functions)
        {
            if (os.str().size())
            {
                os << "|";
            }
            os << pair.first;
        }
        std::cout << "Usage: " << arguments[0] << " [all][" << os.str() << "]" << std::endl;
    }
    else if (arguments.size() == 2 && arguments[1] == "all")
    {
        for (auto pair : functions)
        {
            callList.push_back(pair.second);
        }
    }
    else
    {
        for (auto arg : arguments)
        {
            auto it = functions.find(arg);
            if (it != functions.end())
            {
                callList.push_back(it->second);
            }
        }
    }

    return callList;
}

void terminateHandler()
{
    if (std::current_exception())
    {
        try
        {
            throw;
        }
        catch (const std::exception& ex)
        {
            std::cout << "terminateHandler: " << ex.what() << std::endl;
        }
        catch (...)
        {
            std::cout << "terminateHandler: Unknown exception!" << std::endl;
        }
    }
    else
    {
        std::cout  << "terminateHandler: called without an exception." << std::endl;
    }
    std::abort();
}

int main(int argc, char* argv[])
{
    except::register_for_os_exceptions();
    std::set_terminate(terminateHandler);
   
    auto callList = processArguments(argc, argv);
    for (int i = 0; i < 10 && callList.size(); ++i)
    {
        std::cout << i << "------------------------------------" << std::endl;
        for (auto func : callList)
        {
            func();
        }
        std::cout << "------------------------------------" << i << std::endl;
    }
}

The output of the program should be like this:

0------------------------------------
Message: division by zero
~Message: division by zero
OS exception: division by zero!
Message: reading empty shared_ptr
~Message: reading empty shared_ptr
OS exception: null pointer!
Message: out of bounds vector
~Message: out of bounds vector
OS exception: null pointer!
Message: read from nullptr
~Message: read from nullptr
OS exception: null pointer!
Message: write to nullptr
~Message: write to nullptr
OS exception: null pointer!
------------------------------------0

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:

const char* signalDescription(int sgn)
{
    switch(sgn)
    {
        case SIGABRT: return "SIGABRT";
        case SIGFPE:  return "SIGFPE";
        case SIGILL:  return "SIGILL";
        case SIGINT:  return "SIGINT";
        case SIGSEGV: return "SIGSEGV";
        case SIGTERM: return "SIGTERM";
        default:      return "UNKNOWN";
    }
}

void signalHandler(int sgn)
{
    std::ostringstream os;
    os << "Signal caught: " << signalDescription(sgn) << "(" << sgn << ")";

    signal(sgn, signalHandler);

    throw std::runtime_error(os.str().c_str());
}

void register_for_os_exceptions()
{
    signal(SIGABRT, signalHandler);
    signal(SIGFPE, signalHandler);
    signal(SIGILL, signalHandler);
    signal(SIGINT, signalHandler);
    signal(SIGSEGV, signalHandler);
    signal(SIGTERM, signalHandler);
}

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:

const char* seDescription(const unsigned int& code)
{
    switch (code)
    {
        case EXCEPTION_ACCESS_VIOLATION:         return "EXCEPTION_ACCESS_VIOLATION";
        case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:    return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
        case EXCEPTION_BREAKPOINT:               return "EXCEPTION_BREAKPOINT";
        case EXCEPTION_DATATYPE_MISALIGNMENT:    return "EXCEPTION_DATATYPE_MISALIGNMENT";
        case EXCEPTION_FLT_DENORMAL_OPERAND:     return "EXCEPTION_FLT_DENORMAL_OPERAND";
        case EXCEPTION_FLT_DIVIDE_BY_ZERO:       return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
        case EXCEPTION_FLT_INEXACT_RESULT:       return "EXCEPTION_FLT_INEXACT_RESULT";
        case EXCEPTION_FLT_INVALID_OPERATION:    return "EXCEPTION_FLT_INVALID_OPERATION";
        case EXCEPTION_FLT_OVERFLOW:             return "EXCEPTION_FLT_OVERFLOW";
        case EXCEPTION_FLT_STACK_CHECK:          return "EXCEPTION_FLT_STACK_CHECK";
        case EXCEPTION_FLT_UNDERFLOW:            return "EXCEPTION_FLT_UNDERFLOW";
        case EXCEPTION_ILLEGAL_INSTRUCTION:      return "EXCEPTION_ILLEGAL_INSTRUCTION";
        case EXCEPTION_IN_PAGE_ERROR:            return "EXCEPTION_IN_PAGE_ERROR";
        case EXCEPTION_INT_DIVIDE_BY_ZERO:       return "EXCEPTION_INT_DIVIDE_BY_ZERO";
        case EXCEPTION_INT_OVERFLOW:             return "EXCEPTION_INT_OVERFLOW";
        case EXCEPTION_INVALID_DISPOSITION:      return "EXCEPTION_INVALID_DISPOSITION";
        case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
        case EXCEPTION_PRIV_INSTRUCTION:         return "EXCEPTION_PRIV_INSTRUCTION";
        case EXCEPTION_SINGLE_STEP:              return "EXCEPTION_SINGLE_STEP";
        case EXCEPTION_STACK_OVERFLOW:           return "EXCEPTION_STACK_OVERFLOW";
        default:                                 return "UNKNOWN EXCEPTION";
    }
}

void seTranslator(unsigned int code, struct _EXCEPTION_POINTERS* ep)
{
    if (code == EXCEPTION_ACCESS_VIOLATION || code == EXCEPTION_IN_PAGE_ERROR)
    {
        if (ep->ExceptionRecord->ExceptionInformation[1] == 0)
        {
            throw null_pointer_exception();
        }
    }
    else if (code == EXCEPTION_FLT_DIVIDE_BY_ZERO ||
             code == EXCEPTION_INT_DIVIDE_BY_ZERO)
    {
        throw division_by_zero_exception();
    }

    std::ostringstream os;
    os << "Structured exception caught: " << seDescription(code);

    throw std::runtime_error(os.str().c_str());
}

void register_for_os_exceptions()
{
    _set_se_translator(seTranslator);
}

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:

const char* signalDescription(int sgn)
{
    switch(sgn)
    {
        case SIGABRT: return "SIGABRT";
        case SIGFPE:  return "SIGFPE";
        case SIGILL:  return "SIGILL";
        case SIGINT:  return "SIGINT";
        case SIGSEGV: return "SIGSEGV";
        case SIGTERM: return "SIGTERM";
        default:      return "UNKNOWN";
    }
}

void signalHandler(int sgn, siginfo_t *info, void *)
{
    if (sgn == SIGSEGV && info->si_addr == 0)
    {
        throw null_pointer_exception();
    }

    if (sgn == SIGFPE && (info->si_code == FPE_INTDIV || info->si_code == FPE_FLTDIV))
    {
        throw division_by_zero_exception();
    }

    std::ostringstream os;
    os << "Signal caught: " << signalDescription(sgn) << "(" << sgn << ")";

    throw std::runtime_error(os.str().c_str());
}

void register_for_os_exceptions()
{
    struct sigaction act;

    act.sa_sigaction = signalHandler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO | SA_NODEFER;

    sigaction(SIGABRT, &act, NULL);
    sigaction(SIGFPE, &act, NULL);
    sigaction(SIGILL, &act, NULL);
    sigaction(SIGINT, &act, NULL);
    sigaction(SIGSEGV, &act, NULL);
    sigaction(SIGTERM, &act, NULL);
}

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 smile

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):

0------------------------------------
Message: read from nullptr
0
~Message: read from nullptr
------------------------------------0 

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:

0------------------------------------
Message: division by zero
OS exception: division by zero!
------------------------------------0

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 smile

The benchmark doesn’t use except, but the performance with a division_by_zero_exception should be in the same ballpark.

#include <chrono>
#include <exception>
#include <iostream>

using namespace std;

static uint32_t checkPoint = 100;
static const uint32_t testCount = 1000000000l;

uint64_t toInt(uint64_t value, bool& valid) noexcept
{
    valid = true;
    if (value % checkPoint == 0)
        valid = false;

    if (!valid)
        return value;

    return ++value;
}

uint64_t toInt(uint64_t value)
{
    if (value % checkPoint == 0)
        throw std::invalid_argument("bla bla");
    return ++value;
}

int main()
{
    cout << "Benchmarking exceptions, doing " << testCount << " function calls" << endl;
    while (checkPoint < testCount) {
        cout << "Throw an error every " << checkPoint << " calls" << endl;
        auto startError = chrono::high_resolution_clock::now();
        for (uint64_t test = 0; test < testCount;) {
            bool valid;
            test = toInt(test, valid);
            if (!valid)
                ++test;
        }
        auto stopError = chrono::high_resolution_clock::now();

        auto startThrow = chrono::high_resolution_clock::now();
        for (uint64_t test = 0; test < testCount;) {
            try {
                test = toInt(test);
            } catch (...) {
                ++test;
            }
        }
        auto stopThrow = chrono::high_resolution_clock::now();
        auto errorTicks = (stopError - startError).count();
        auto throwTicks = (stopThrow - startThrow).count();

        cout << "Error ticks " << errorTicks << endl << "Throw ticks " << throwTicks << endl;
        if (errorTicks > throwTicks) {
            auto ratio = double(errorTicks)/double(throwTicks) ;
            cout << "Throw is x" <<  ratio << " times (" << (ratio -1) * 100 << "%) faster" << endl;
        } else {
            auto ratio = double(throwTicks)/double(errorTicks);
            cout << "Error is x" <<  ratio << " times (" << (ratio -1) * 100 << "%) faster" << endl;
        }
        cout << "-------------------------------" << endl;
        checkPoint *= 10;
    }
    return 0;
} 

Binary compiled with Visual C++ 2015 Update 3 x64 performed on my Lenovo W510 i7 like this:

Benchmarking exceptions, doing 1000000000 function calls
Throw an error every 100 calls
Error ticks 12923327818
Throw ticks 32912109611
Error is x2.54672 times (154.672%) faster
-------------------------------
Throw an error every 1000 calls
Error ticks 12709068503
Throw ticks 9856135615
Throw is x1.28946 times (28.9458%) faster
-------------------------------
Throw an error every 10000 calls
Error ticks 12406945748
Throw ticks 8314639531
Throw is x1.49218 times (49.2181%) faster
-------------------------------
Throw an error every 100000 calls
Error ticks 12133724149
Throw ticks 7524532681
Throw is x1.61256 times (61.2555%) faster
-------------------------------
Throw an error every 1000000 calls
Error ticks 11891683998
Throw ticks 7277094208
Throw is x1.63413 times (63.4125%) faster
-------------------------------
Throw an error every 10000000 calls
Error ticks 11875875947
Throw ticks 7263120632
Throw is x1.63509 times (63.5093%) faster
-------------------------------
Throw an error every 100000000 calls
Error ticks 11922168230
Throw ticks 7265200344
Throw is x1.641 times (64.0996%) faster
-------------------------------

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).

Comments