Can I throw a C++ exception from a structured exception?


A customer wanted to know if it was okay to throw a C++ exception from a structured exception.

They explained that they didn't want to compile their project with the /EHa switch, which instructs the compiler to use the exception-handling model that catches both asynchronous (structured) exceptions as well as synchronous (C++) exceptions. In other words, the catch statement will catch both explicitly thrown C++ exceptions (raised by the throw statement) as well as exceptions generated by the operating system, either due to notifications from the CPU (such as an access violation or divide-by-zero) or explicit calls to Raise­Exception.

The customer explained that they didn't want to use /EHa because doing so significantly impairs compiler optimizations and results in larger code size. But on the other hand, they do want to catch the asynchronous (structured) exceptions.

So they had a fiendish plan.

Their fiendish plan is to install an unhandled exception filter which turns around and throws the C++ exception. That way, a structured exception will result in a standard C++ exception, but without the code generation penalties of the /EHa compiler option.

// This clever function is an exception filter that converts
// asynchronous exceptions (structured exception handling)
// to synchronous exceptions (C++ exceptions).

LONG WINAPI CleverConversion(
    EXCEPTION_POINTERS* ExceptionInfo)
{
    
    auto record = ExceptionInfo->ExceptionRecord;

    std::string message;
    ... build a message based on the exception code and
    other parameters ...

    throw std::exception(message.c_str());
}

int sample_function(int* p)
{
    try {
        printf("About to dereference the pointer %p\n", p);
        return *p;
    } catch (std::exception& e) {
        Log(e.what());
    }
    return 0;
}

int __cdecl main(int argc, char **argv)
{
    SetUnhandledExceptionFilter(CleverConversion);

    return sample_function(nullptr);
}

Neat trick, huh? All the benefits of /EHa without the overhead!

Well, except that they found that it didn't always work.

In the example above, the catch did catch the C++ exception, but if they took out the printf, then the exception was not caught.

int sample_function(int* p)
{
    try {
        return *p;
    } catch (std::exception& e) {
        Log(e.what());          // exception not caught!
    }
    return 0;
}

The customer wanted to know why the second version didn't work.

Actually the first version isn't guaranteed to work either. It happens to work because the compiler must consider the possibility that the printf function might throw a C++ exception. The printf function is not marked as noexcept, so the possibility is in play. (Not that you'd expect it to be marked as such, seeing as it's a C function, and C doesn't have exceptions.) When the access violation is raised as a structured exception, the Clever­Conversion function turns it into a C++ exception and throws it, at which point the try block catches it. But the try block is not there for the Clever­Conversion exception. It's there to catch any exceptions coming out of printf, and you just happened to be lucky that it caught your exception too.

In the second example, there is no call to printf, so the compiler says, "Well, nothing inside this try block can throw a C++ exception, so I can optimize out the try/catch." You would also have observed this behavior if there were function calls inside the try block, if the function calls were all to functions that were marked noexcept or if the compiler could prove that they didn't throw any C++ exceptions (say, because the function is inlined).

This answers the question, but let's try to look at the whole story.

  1. We want to use /EHa.
  2. But the documentation says that /EHa results in less efficient code. We want more efficient code, not less.
  3. Aha, we found this trick that lets us convert asynchronous exceptions to synchronous ones. Now we get all the benefits of /EHa without any of the costs!

It looks like you found some free money on the ground, but is it really free money?

The customer seems to think that the /EHa option results in less efficient code simply because the compiler team is a bunch of jerks and secretly hates you.

No, that's not why the /EHa option results in less efficient code. The possibility that any memory access or arithmetic operation could trigger an exception significantly impairs optimization opportunities. It means that all variables must be stable at the point memory accesses occur.

Consider the following code fragment:

class Reminder
{
public:
    Reminder(char* message) : m_message(message) { }
    Reminder() { std::cout << "don't forget to "
                           << m_message << std::endl; }

    void UpdateMessage(char* message) { m_message = message; }

private:
    char* m_message;
};

void NonThrowingFunction() noexcept;
void DoSomethingElse(); // might throw

void sample_function()
{
    try {
        Reminder reminder("turn off the lights");
        if (NonThrowingFunction()) {
            reminder.UpdateMessage("feed the cat");
        }
        DoSomethingElse();
    } catch (std::exception& e) {
        Log(e.what());
    }
}

If compiling without /EHa, the compiler knows that the Non­Throwing­Function function cannot throw a C++ exception, so it can delay the store of reminder.m_message to just before the call to Do­Something­Else. In fact, it is like to do so because it avoids a redundant store.

The pseudo-code for this function might look like this:

    allocate 4 bytes in local frame for reminder

l1:
    call NonThrowingFunction
    if result is zero
        load r1 = "turn off the lights"
    else
        load r1 = "feed the cat"
    endif
    store r1 to reminder.m_message
    call DoSomethingElse
l2:
    std::cout << "don't forget to "
              << r1 << std::endl;
l3:

    clean up local frame
    return

if exception occurs between l1 and l2
    std::cout << "don't forget to "
              << reminder.m_message << std::endl;
    fall through

if exception occurs between l2 and l3
    if exception is std::exception
        Log(e.what())
        goto l3
    else
        continue exception search
    endif

Notice that we optimized out a redundant store by delaying the initialization of reminder, and we enregistered reminder.m_message in the common code path. Delaying the initialization of reminder is not an optimization available to /EHa because of the possibility that Non­Throwing­Function might raise an asynchronous exception that gets converted to a synchronous one:

    allocate 4 bytes in local frame for reminder

l0:
    // cannot delay initialization of reminder
    load r1 = "turn off the lights"
    store r1 to reminder.m_message

l1:
    call NonThrowingFunction
    if result is nonzero
        load r1 = "feed the cat"
        store r1 to reminder.m_message
    endif
    call DoSomethingElse
l2:
    std::cout << "don't forget to "
              << r1 << std::endl;
l3:

    clean up local frame
    return

if exception occurs between l1 and l2
    std::cout << "don't forget to "
              << reminder.m_message << std::endl;
    fall through

// and there is a new exception region
if exception occurs between l0 and l1, or between l2 and l3
    if exception is std::exception
        Log(e.what())
        goto l3
    else
        continue exception search
    endif

The extra code is necessary in order to ensure that the reminder variable is in a stable state before calling Non­Throwing­Function. In general, if you turn on /EHa, the compiler must ensure that every object which is accessed outside the try block (eith explicitly in code or implicitly via an unwind destructor) is stable in memory before performing any operation that could result in an asynchronous exception, such as accessing memory.

This requirement that variables be stable in memory comes at a high cost, because it not only forces redundant stores to memory, but it also prohibits various types of optimizations based on out-of-order operations.

The Clever­Conversion is basically a manual replication of what /EHa does, but lying to the compiler and saying, "Um, yeah, don't worry about asynchronous exceptions."

Observe what happens if an asynchronous exception occurs inside Non­Throwing­Function even though you compiled without the /EHa flag:

We destruct the reminder object, which means printing the m_message to std::cout. But the non-/EHa version did not ensure that reminder.m_message was stable. Indeed, if an exception occurs inside Non­Throwing­Function, we will try to print reminder.m_message anyway, even though it is an uninitialized variable.

Printing an uninitialized variable is probably not what the program intended.

So a more complete answer to the scenario is "Yes, it is technically possible to throw a C++ exception from a structured exception handler, but doing so requires that the program be compiled with /EHa in order to avoid undefined behavior."

And given that avoiding the /EHa flag was the whole purpose of the exercise, the answer to the specific scenario is, "No, this doesn't work. Your program will behave in undefined ways."

Comments (21)
  1. Damien says:

    I always wonder at the thought process behind this sort of thing. If option X has a performance penalty and you believe that option Y can achieve exactly the same as option X but at a fraction of the cost, shouldn’t you be wondering why option X isn’t implemented as option Y already?

    1. Joshua says:

      It seems to me they don’t care too much about process memory corruption and just want to run catch blocks and dtors during unwind.

      If not for their example being what it was I would think they don’t care about access exceptions from their own code but only ones from Windows.

      Expected useless followup: can I call longjump() in a n unhandled exception filter.

    2. Antonio Rodríguez says:

      [Mediocre] people tend to underestimate other people’s work. You have already hear it: “this application worked in Windows XP, but Microsoft purposely broke the API in Windows 7”. Or another one, more than 20 years old by now: “Microsoft has some dark agreement with memory makers; I know that because Windows 95 requires more memory than Windows 3.1”. It’s like Microsoft engineers have a magic wand that allows to make new versions of Windows which add features without breaking compatibility or using more resources; but they choose not to use it. (I talk about Microsoft because of this blog’s policy of not talking about other companies’ products, but Microsoft isn’t alone here, of course).

      Similarly, many programmers seem to think that compiler optimizations are black magic, not unlike the mentioned magic wand, and that all you have to do is to wave it, say “bibidi babidi boo”, and… voilà! Instant magic! And look, without any side effects at all! If it doesn’t work, it surely is that engineers are fool or perverse (or both!).

      1. Darran Rowe says:

        I also find that many programmers don’t understand optimisations and what they do.
        There is still a lot of people who think that the optimiser just gives out a more optimised version of what you put in, but it does exactly the same thing. They don’t seem to understand that what you get out behaves exactly the same, but is not required to actually do exactly the same thing.

        1. Joshua says:

          > There is still a lot of people who think that the optimiser just gives out a more optimised version of what you put in, but it does exactly the same thing.

          Because it was once true. Among the first useful optimizations was “suppress redundant reloads”; but when it was new there was a switch to turn it off in case somebody was abusing undefined behavior as this was also the first one that radically changed what undefined behavior did.

        2. Kevin says:

          Wikipedia has a surprisingly thorough set of articles about individual compiler optimizations (strength reduction, loop unswitching, etc.). They’re very useful for disabusing people of this notion.

          Or at least, I imagine that they are. I haven’t actually tried using them for that purpose.

      2. xcomcmdr says:

        Or people who think that anti-virus editors make viruses in order to sell anti-virus solutions.

        Or people that did not at all study or code Windows, but are convinced that Windows 95 is only a GUI for DOS, and that Windows 7 still has DOS as a kernel.

        “Those people”. Ugh ! They are so ridiculous !

    3. Aged .Net Guy says:

      The other mistaken thought process I’ve encountered many times over the years is …

      Goofy programmer: “The compiler inserts all that bloat because it needs that to protect against extreme corner cases and such. My plain vanilla code doesn’t use [insert level of API or coding tech I don’t understand] so I won’t be triggering whatever that bloat might be needed for.”

      Doesn’t matter if the goof thinks the bloat is there to protect when using COM, or multithreading, or lambdas, or ATL or whatever. It’ll always be something that A) the goof doesn’t understand, and B) the goof isn’t directly using in the small part of the complete executable he’s actually thinking about. All bets are off as to the rest of the complete execution environment; it may be chock full of that stuff but it’s invisible to him (very, very occasionally her).

  2. Ben Voigt says:

    The problem is the mindset that C++ language exceptions are “synchronous”, which unfortunately Raymond is repeating.

    C++ exceptions are synchronous when the `throw` statement is reached from the thread entry point, and asynchronous when the `throw` statement is called in asynchronous context. Structured exception handlers are a Win32 example of asynchronous context (but not the only one, you can also have code that does SuspendThread and then messes with the instruction pointer — I believe that .NET Thread.Abort() is an example). Signal handlers are a POSIX example.

    Now that we see that “C++ language exceptions only” is not sufficient to exempt us from asynchronous exceptions, we also know it’s not sufficient for turning off /EHa.

    Ultimately though, this still fails to identify all cases where /EHa is needed, because /EHa is *also* critical if throwing exceptions across module boundaries. Probably there ought to be a separate flag for forcing the use of OS exceptions. And if both /EHa and the hypothetical /EHos trigger the same compiler behavior, so be it. In the future they might not (In particular, when using /EHs /EHos it might be possible for the compiler to eliminate handlers when a try block contains only in-module noexcept calls, while still using OS infrastructure and handlers when calling foreign functions).

    1. kantos says:

      Nitpickers Corner: This actually gets a LOT more complicated when you bring in the concept of coroutines, which are extremely asynchronous and the exception may not even be “caught” on the same thread it was thrown.

    2. Harry Johnston says:

      On the face of it, it doesn’t seem reasonable for the programmer to expect C++ exceptions to work in an asynchronous context. Is there some reason I’m not aware of, e.g., is it mentioned in the C++ standard, or the POSIX standard, perhaps?

  3. kantos says:

    Honestly Clang and GCC treat any function from the Cstdlib as noexcept which the standard allows, I’m honestly surprised that the Visual C++ libraries team didn’t just use a macro to support C++ and C on that matter. Or just let the compiler figure it out.

    1. Doug says:

      Why, what a clever idea. I wonder why the MSVC compiler guys never thought of that. They must be very silly indeed.

      Oh, wait, what is this /EHc compiler switch? I wonder what it does.

      1. Martin Ba. _ says:

        @Doug: Yeah, except that /EHsc marks *all* extern “C” Functions as noexcept, not just the standard library. Quite the difference, if you ask me. Being able to use /EHs (w/o ‘c’) and still benefiting from the static knowledge that the myriad of cstdlib functions won’t ever throw a C++ exception could be a good thing — because you’r C-Interface libraries or other third party C-Api wrappers very well might.

  4. Andre says:

    I seem to remember reading somewhere (the official docs, Raymond’s blog, not sure) that using /EHa is a bad idea. I’m not quite sure why, maybe because you can wrongly handle things like guard pages or because you catch things you should never catch at all, like access violations.

    Do I remember this wrong or does anybody have details? Of course you could argue “if that were the case, surely this feature wouldn’t even exist”, but the answer to that is probably “compatibility”.

    1. M Hotchin says:

      People using /EHa are tempted to use ‘catch (…)’. This eats exceptions that are used by the OS for things like growing your stack.
      https://blogs.msdn.microsoft.com/oldnewthing/20060927-07/?p=29563

  5. Ivan K says:

    Hopefully fancy logging api can capture and log those fancy exceptions shmexceptions for posterity.

  6. Neil says:

    The linked page says, “you can’t mix the SEH syntax with try, throw, and catch in the same function”. So can you not even “protect” against a structured exception from e.g. a call to a specific third-party library?

    1. Darran Rowe says:

      You can, but since most SEH exceptions that you would catch with the try/except or try/finally syntax are going to be things like STATUS_ACCESS_VIOLATION or STATUS_GUARD_PAGE_VIOLATION. So the question is why would you want to handle those?
      If for some reason the third party library is using SEH to raise exceptions that you would want to catch, notice that it says in the same function? You can catch both types if you go through to different functions.
      But here is one thing to remember about C++ exceptions, besides bad libraries, they are used mostly in exceptional situations. So the majority of my exception handling is a try/catch block out not that far away from my main function. All it does is catch the error, log details and then exits the process. It is best to do this since you will be getting the exception closest to the error, and lets face it, if you are getting exceptions thrown about memory allocation failure, or bounds errors or other things like that, something has messed up with your application and you don’t want it to continue.

  7. Killer{R} says:

    BTW, there is special C function that intentially created to translate SEH to C++ exceptions: _set_se_translator
    I believe its better to be used for such purpose than plain SEH filter.
    However its documentation clearly requires using of /EHa that also should hint potential revolutionaries that SetUnhandledExceptionFilter may be not so good as they expect..

  8. Simon Clarkstone says:

    Typo: AFAICT you missed the “~” off your destructor declaration for Reminder().

Comments are closed.

Skip to main content