Re: Implementing exceptions of C++ in C

From: Mark A. Gibbs (x_gibbsmark_at_rogers.com_x)
Date: 04/17/04


Date: Sat, 17 Apr 2004 19:20:29 GMT


Bernard wrote:

> Thus far, all replies are useful but let me explain ...
>
> OK, if you cannot write translation of the C++ code in parallel
> C for whatever reason - that I will try to understand later - can
> you explain program flow. This is what is not clear to me. If you

int foo1()
{
    int bar = 1;
    throw bar;
    return 2;
}

int foo2()
{
    float bar = 1.0f;
    throw bar;
    return 2;
}

int main()
{
    int result = EXIT_FAILURE;

    try
    {
       foo1();
       foo2();
       result = EXIT_SUCCESS;
    }
    catch(int x)
    {
       printf("int exception caught: %d", x);
    }
    catch(...)
    {
       printf("unknown exception caught");
    }

    return result;
}

OK, it works like this. main() starts as usual, and executes as usual up
until it hits the "try". At that point, the compiler has noted that any
exceptions thrown inside that block are handled by one of the handlers
immediately following it. Then execution continues.

When it gets to foo1(), foo1() throws an exception of type int.
Immediately normal execution stops, and the program starts looking for
the exception handler that will be responsible for handling the int
exception. There is no "try" block in foo1(), so it just leaves it
(cleaning up anything that needs to be cleaned up along the way) without
executing the return statement and climbs back up the call stack up a
step. Then it's in main(), and main *does* contain a "try" block. So it
checks the handlers ("catch" statements) at the end of the try block to
see if any matches. Note, it does NOT call foo2() or the next statement,
it goes immediately to the handler.

Because there is a handler for int, it goes there, and inside the
exception, it prints "int exception caught: 1" (I hope, I don't use
printf anymore). Once it gets to the end of the catch block, it skips to
the end of all the exception handlers, and executes the return statement.

So linearly, this is what happened:

main starts:
    main: int result = EXIT_FAILURE;
    main: foo1();
    foo1 starts:
       foo1: int bar = 1;
       foo1: throw bar;
       int exception thrown (value: 1), immediately leaves foo1
    jumps to int exception handler
    main: sets x = 1 (from the exception)
    main: printf("int exception caught: %d", x);
    finished exception handler
    main: return result;
main ends:

Now if you comment out the throw statement in foo1, you get this instead:

main starts:
    main: int result = EXIT_FAILURE;
    main: foo1();
    foo1 starts:
       foo1: int bar = 1;
       foo1: return 2;
    foo1 ends:
    foo2 starts:
       int exception thrown (value: 1), immediately leaves foo2
    looks for float exception handler, can't find it, sees a "catch-all"
handler, jumps there
    main: printf("int exception caught: %d", x);
    finished exception handler
    main: return result;
main ends:

The mechanics of it are pretty complex, and that without nesting try
blocks or other complexities. It's probably better to think of
exceptions in terms of what they are there for. Consider the following
example:

vector3d shrink_vector(vector3d v, int scale_factor)
{
    v.x /= scale_factor;
    v.y /= scale_factor;
    v.z /= scale_factor;
    return v;
}

vector3d make_unit_vector(vector3d v)
{
    // calculates vector's length. not important, so not included
    int length = get_vector_length(v);
    return shrink_vector(v, length);
}

void high_level_function()
{
    vector3d v = get_vector_somehow();
    vector3d u = make_unit_vector();

    printf("Your unit vector is: (%d, %d, %d)", u.x, u.y, u.z);
}

Now, take a look at shrink_vector(). What if the scale_factor is 0? You
can't divide by zero, that would be a disaster. But what on earth is
shrink_vector() supposed to do if you send it a 0? Should it silently
convert it to 1? No, that makes no sense. Return vector (0, 0, 0)? No,
that also makes no sense, mathematically. It could call some kind of
error function, but which one? You won't be able to reuse the function
easily if you have to use it with the same error function.

Clearly, the best think for shrink_vector to do is pass the buck. After
all, it wasn't responsible for the 0 and it couldn't fix it. So rewrite
it as:

vector3d shrink_vector(vector3d v, int scale_factor)
{
    if (scale_factor == 0)
    {
       divide_by_zero_exception dbz;
       throw dbz;
    }

    v.x /= scale_factor;
    v.y /= scale_factor;
    v.z /= scale_factor;
    return v;
}

Now, what about make_unit_vector()? It would know if it was trying to
make a zero-vector a unit vector after it finds the length, but what
should it do about it? Again, you *could* put the error handling in
there, but that would limit the reuse. So, do nothing.

Now, high_level_function should know what to do if there is a problem.
So rewrite it like this:

void high_level_function()
{
    try
    {
       vector3d v = get_vector_somehow();
       vector3d u = make_unit_vector();

       printf("Your unit vector is: (%d, %d, %d)", u.x, u.y, u.z);
    }
    catch (divide_by_zero_exception x)
    {
       printf("You entered a zero vector.");
    }
    // NOTE: any exceptions that high_level_function does
    // not need to know how to handle, get passed up to whoever
    // called it (eg. an out of memory exception)
}

So all the exception handling is in one place, and that's in a high
level function that knows what to do about it. As your program gets
smarter, it scales nicely. For example, suppose that in the
get_vector_length() function, calculating the length turns out to cause
integer overflow. No problem, just throw an integer_overflow_exception
there, and add a catch block in high_level_function to handle it.

I have tried to keep the examples as C-like as possible, but obviously,
this is a C++ phenomenon. That opens up a whole new world of
constructors and destructors, and the possiblity of using polymorphic
objects as exception types (in the example above, both
divide_by_zero_exception and integer_overflow_exception could derive
from math_exception, then you would only need to catch that, unless you
were interested in the specifics), among other things. Some aspects of
exception handling can be mimed in C, but since C is not polymorphic
object aware and does not know about destructors, there would be a
forbidding amount of work to get the full power without introducing a
whole whack of bugs (and by that I mean work not only on the side of the
guy making them, I mean also work by the guy trying to use them).

mark



Relevant Pages

  • Re: Implementing exceptions of C++ in C
    ... When it gets to foo1(), foo1throws an exception of type int. ... it goes immediately to the handler. ...
    (comp.lang.cpp)
  • Re: math co-processor exceptions
    ... :> status word, but no handler is invoked. ... Maybe be a stupid question, but what have you set the current exception ... then forwards its task to INT 02h). ... operation and manually invoke these handlers via INT calls? ...
    (comp.lang.asm.x86)
  • Re: Frame-based exception handling problem on Server 2008
    ... If I set a breakpoint to our exception ... points to the default handler, our exception handler is called just fine. ... typedef struct _exception_list ...
    (microsoft.public.win32.programmer.kernel)
  • Re: Frame-based exception handling problem on Server 2008
    ... If I set a breakpoint to our exception ... points to the default handler, our exception handler is called just fine. ... with the appropriate Visual-C++ syntax. ...
    (microsoft.public.win32.programmer.kernel)
  • Re: Try Finally...
    ... Now we can protect every more or less specialized action ... an error indication (exception or error code) has to be ... an according exception handler can be inserted into the code. ... resources, as they are related to specific actions. ...
    (comp.lang.pascal.delphi.misc)