Free VC++ Tutorial

Web based School

Previous Page Main Page Next Page


17 — Exception Handling

The Win32 API supports structured exception handling. Through this mechanism, applications can handle various hardware- and software-related conditions. Structured exception handling is not to be confused with the concept of exceptions in the C++ language, which is a feature of that language. The Win32 exception handling mechanism is not dependent on its implementation in a specific language. To avoid confusion, I decided to follow the conventions used in Microsoft documentation and use the term "C exception" to refer to Win32 structured exceptions, and "C++ exception" to refer to the typed exception handling mechanism of the C++ language.

Exception Handling in C and C++

Microsoft provides a set of extensions to the C language, which enable C programs to handle Win32 structured exceptions. This exception handling mechanism is markedly different from the typed exceptions in the C++ language. This section offers a review of both mechanisms in the context of exceptions in the Win32 environment.

C Exceptions

What is, indeed, an exception? How do exceptions work? In order to understand the exception handling mechanism, first take a look at the program shown in Listing 17.1.

    Listing 17.1. A program that generates an exception.
void main(void)

{

    int x, y;

    x = 5;

    y = 0;

    x = x / y;

}

Needless to say, an integer division by zero is likely to cause a program to terminate abnormally. If you compile the above program and run it under Windows 95, it generates the dialog shown in Figure 17.1.


Figure 17.1. Division by zero error.

What exactly happened here? Obviously, when you attempt to divide by zero, the processor will generate an error condition (the actual mechanism is hardware dependent and not of our concern). This error condition is detected by the operating system, which looks for an exception handler for the specific error condition. As no such handler was detected, the default exception handling mechanism took over, displaying the dialog.

Using the C exception handling mechanism, it is possible for us to catch this exception and handle the divide by zero condition gracefully. Consider the program shown in Listing 17.2.

    Listing 17.2. Handling the divide by zero exception.
#include "windows.h"

void main(void)

{

    int x, y;

    __try

    {

        x = 5;

        y = 0;

        x = x / y;

    }

    __except (GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ?

              EXCEPTION_EXECUTE_HANDLER :

              EXCEPTION_CONTINUE_SEARCH)

    {

        printf("Divide by zero error.\n");

    }

}

Running this program no longer produces the dialog shown in Figure 17.1; instead, the message "Divide by zero error." is printed and the program terminates gracefully.

The block of statements following the __try instruction is often called a guard block. This block of statements is executed unconditionally. When an exception is raised within the guard block, the expression following the __except statement (often called the filter expression) is evaluated. This expression should be an integer expression yielding one of the following values:

    Table 17.1. Filter expression values.
Symbolic constant


Value


Description


EXCEPTION_CONTINUE_EXECUTION

-1

Continue execution at the location where exception was raised

EXCEPTION_CONTINUE_SEARCH

0

Pass control to next exception handler

EXCEPTION_EXECUTE_HANDLER

1

Execute exception handler

If the filter expression's value is -1 (EXCEPTION_CONTINUE_EXECUTION), execution continues at the location where the exception was raised. That is, at the location, not afterwhich means that the offending piece of code may get executed again. Whether it actually does get executed or not depends on the type of the exception. For example, in the case of an integer division by zero, it does; in the case of a floating-point division by zero, it does not. In any case, care should be taken to avoid creating an infinite loop by returning control to the point where the error occurs without eliminating the conditions which caused the exception in the first place.

In the other two cases, the first thing that happens is that the guard block goes out of scope. Any function calls that might have been interrupted by the exception are terminated and the stack is unwound.

If the filter expression evaluates to 1 (EXCEPTION_EXECUTE_HANDLER), control is transferred to the statement block following the __except statement.

The third filter value, 0 (EXCEPTION_CONTINUE_SEARCH), hints at the possibility of nested exceptions. Indeed, consider the program shown in Listing 17.3. In this program, two exceptions are generated, one for a floating-point division by zero, one for an integer division by zero. The two exceptions are handled very differently.

    Listing 17.3. Nesting exception handlers.
#include <stdio.h>

#include <float.h>

#include <windows.h>

unsigned int divzerofilter(unsigned int code, int *j)

{

    printf("Inside divzerofilter\n");

    if (code == EXCEPTION_INT_DIVIDE_BY_ZERO)

    {

        *j = 2;

        printf("Handling an integer division error.\n");

        return EXCEPTION_CONTINUE_EXECUTION;

    }

    else return EXCEPTION_CONTINUE_SEARCH;

}

void divzero()

{

    double x, y;

    int i, j;

    __try

    {

        x = 10.0;

        y = 0.0;

        i = 10;

        j = 0;

        i = i / j;

        printf("i = %d\n", i);

        x = x / y;

        printf("x = %f\n", x);

    }

    __except (divzerofilter(GetExceptionCode(), &j))

    {

    }

}

void main(void)

{

    _controlfp(_EM_OVERFLOW, _MCW_EM);

    __try

    {

        divzero();

    }

    __except (GetExceptionCode() == EXCEPTION_FLT_DIVIDE_BY_ZERO ?

              EXCEPTION_EXECUTE_HANDLER :

              EXCEPTION_CONTINUE_SEARCH)

    {

        printf("Floating point divide by zero error.\n");

    }

}

When an exception is raised inside the divzero function, the filter expression is evaluated. This results in a call to the divzerofilter function. The function checks if the exception was an integer division by zero exception; if so, it corrects the value of the divisor (j) and returns the EXCEPTION_CONTINUE_EXECUTION value, which causes the exception handling mechanism to return control to the point where the exception was raised. In the case of any other exceptions, divzerofilter returns EXCEPTION_CONTINUE_SEARCH; this causes the exception handling mechanism to seek another exception handler.

This other exception handler has been installed in the main function. This handler handles floating-point division by zero exceptions. Instead of returning to the point where execution was interrupted, it simply prints an error message.

Running this program produces the following output:

Inside divzerofilter

Handling an integer division error.

i = 5

Inside divzerofilter

Floating point divide by zero error.

As you can see, both times an exception is raised, the exception filter installed in the function divzero is activated. However, in the case of the floating-point division, the exception remains unhandled; therefore, the exception is propagated to the next level, the exception handler installed in the main function.


NOTE: To handle floating-point exceptions, it was necessary to call the _controlfp function. This function can be used to enable floating-point exceptions. By default, floating-point exceptions on the Intel architecture are disabled; instead, the floating-point library generates IEEE-compatible infinite results.

A discussion of C exception handling would not be complete without a list of some of the commonly occurring C exceptions. These exceptions are shown in Table 17.2.

    Table 17.1. Filter expression values.
Symbolic constant


Description


EXCEPTION_ACCESS_VIOLATION

Reference to invalid memory location

EXCEPTION_PRIV_INSTRUCTION

Attempt to execute privileged instruction

EXCEPTION_STACK_OVERFLOW

Stack overflow

EXCEPTION_FLT_DIVIDE_BY_ZERO

Floating-point division

EXCEPTION_FLT_OVERFLOW

Floating point result too large

EXCEPTION_FLT_UNDERFLOW

Floating point result too small

EXCEPTION_INT_DIVIDE_BY_ZERO

Integer division

EXCEPTION_INT_OVERFLOW

Integer result too large

In addition to system-generated exceptions, applications can raise software exceptions using the RaiseException function. Windows reserves exception values with bit 29 set for user-defined software exceptions.

C Termination Handling

Closely related to the handling of C exceptions is the topic of C termination handling. To better understand the problem of which termination handling provides a solution, consider the program shown in Listing 17.4.

    Listing 17.4. Resource allocation problem.
#include <stdio.h>

#include <windows.h>

void badmem()

{

    char *p;

    printf("allocating p\n");

    p = malloc(1000);

    printf("p[1000000] = %c\n", p[1000000]);

    printf("freeing p\n");

    free(p);

}

void main(void)

{

    __try

    {

        badmem();

    }

    __except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?

              EXCEPTION_EXECUTE_HANDLER :

              EXCEPTION_CONTINUE_SEARCH)

    {

        printf("An access violation has occurred.");

    }

}

In this program, the function badmem allocates the p character array. However, its execution is interrupted when it refers to an invalid array element. Because of this, the function never has a chance to free up the allocated array, as demonstrated by its output:

allocating p

An access violation has occurred.

This problem can be solved by installing a termination handler in the badmem function, as shown in Listing 17.5.

    Listing 17.5. A termination handler.
#include <stdio.h>

#include <windows.h>

void badmem()

{

    char *p;

    __try

    {

        printf("allocating p\n");

        p = malloc(1000);

        printf("p[1000000] = %c\n", p[1000000]);

    }

    __finally

    {

        printf("freeing p\n");

        free(p);

    }

}

void main(void)

{

    __try

    {

        badmem();

    }

    __except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?

              EXCEPTION_EXECUTE_HANDLER :

              EXCEPTION_CONTINUE_SEARCH)

    {

        printf("An access violation has occurred.");

    }

}

Running this program produces the desired result:

allocating p

freeing p

An access violation has occurred.

As you can see, the instructions in the badmem function are now enclosed in a __try block, which is now followed by the __finally keyword. The __finally keyword is special in that the instruction block that follows it is always executed, no matter under what circumstances the function terminates. So when badmem goes out of scope due to the exception, the instructions in the __finally block are given a chance to clean up any resources that might have been allocated within this function.

C++ Exception Handling

The Win32 exception handling mechanism uses the GetExceptionCode function to determine the nature of the exception. In contrast, C++ exception handling is type-based; the nature of the exception is determined by its type.

Most examples that demonstrate C++ exception handling do so in the context of a class declaration. This is not necessary, and in my opinion often hides the simplicity of C++ exception handling. Consider the simple example in Listing 17.6. (When you compile this example or any other program that uses C++ exceptions, do not forget to add the -GX switch to the cl command line.)

    Listing 17.6. C++ Exception handling.
#include <iostream.h>

int divide(int x, int y)

{

    if (y == 0) throw int();

    return x / y;

}

void main(void)

{

    int x, y;

    try

    {

        x = 5;

        y = 0;

        x = divide(x, y);

    }

    catch (int)

    {

        cout << "A division by zero was attempted.\n";

    }

}

In this example, the function divide raises an exception of type int when a division by zero is attempted. This exception is caught by the exception handler in main.

Termination Handling in C++

C++ exceptions can also be used for termination handling. For termination handling, a C++ program can wrap a block of code using a "catchall" exception handler, and perform resource cleanup before propagating all exceptions to a higher level handler by using throw. Consider the example in Listing 17.7, which is a C++ variant of the program shown in Listing 17.5.

    Listing 17.7. Termination handling with C++ exceptions.
#include <stdio.h>

#include <windows.h>

void badmem()

{

    char *p;

    try

    {

        printf("allocating p\n");

        p = (char *)malloc(1000);

        printf("p[1000000] = %c\n", p[1000000]);

    }

    catch(...)

    {

        printf("freeing p\n");

        free(p);

        throw;

    }

}

void main(void)

{

    try

    {

        badmem();

    }

    catch(...)

    {

        printf("An exception was raised.");

    }

}

Running this program produces the following output:

allocating p

freeing p

An exception was raised.

The exception handler in the function badmem plays the role of the __finally block in the C exception handling mechanism.

Although these examples demonstrate the power of C++ exception handling with C-style code, the use of classes in exception handling has some obvious advantages. For example, when the exception is thrown, an object of the type of the exception is actually created; thus it is possible to provide additional information about the exception in the form of member variables. Also, appropriate use of constructors and destructors can replace the relatively inelegant resource cleanup mechanism shown in Listing 17.7.

C++ Exception Classes

Visual C++ Version 4.0 provides an implementation of the exception class hierarchy, as put forward in the draft ANSI C++ standard. This hierarchy consists of the exception class and derived classes representing various conditions, such as run-time errors. The exception class and derived classes are declared in the header file stdexcpt.h. Because these classes are based on an evolving draft standard, it is possible that they will change with future releases of Visual C++.

Mixing C and C++ Exceptions

While the C compiler does not support C++ exceptions, the C++ compiler supports both C++ exceptions and the Microsoft extensions for C exceptions. Sometimes it is necessary to mix these two in order to use the C++ exception syntax while catching Win32 structured exceptions. There are basically two methods for this: You can use an ellipsis handler, or you can use a translator function.

The Ellipsis Handler

In the termination handling example shown in Listing 17.7, we already made use of the ellipsis handler. This catchall handler, which has the form

catch(...)

{

}

can be used to catch exceptions of any type, including C exceptions. This offers a simple exception handling mechanism like the one used in Listing 17.7. Unfortunately, the ellipsis handler does not have any information about the actual type of the structured exception.

This should be easy, you say. (Well, I certainly said that when I first read about the differences between C and C++ exception handling.) Why not just catch an exception of type unsigned int (after all, the Microsoft Visual C++ documentation states that this is the type of C exceptions) and examine its value? Consider the program in Listing 17.8:

    Listing 17.8. Failed attempt to catch C exceptions as C++ exceptions of type unsigned int.
#include <windows.h>

#include <iostream.h>

void main(void)

{

    int x, y;

    try

    {

        x = 5;

        y = 0;

        x = x / y;

    }

    catch (unsigned int e)

    {

        if (e == EXCEPTION_INT_DIVIDE_BY_ZERO)

        {

            cout << "Division by zero.\n";

        }

        else throw;

    }

}

Alas, this elegant solution is no solution at all. C exceptions can only be caught by an ellipsis handler. But not all is lost just yet; could we not simply use the GetExceptionCode function in the C++ catch block and obtain the structured exception type? For example, consider the program in Listing 17.9.

    Listing 17.9. C++ exception handlers cannot call GetExceptionCode.
#include <windows.h>

#include <iostream.h>

void main(void)

{

    int x, y;

    try

    {

        x = 5;

        y = 0;

        x = x / y;

    }

    catch (...)

    {

        // The following line results in a compiler error

        if (GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO)

        {

            cout << "Division by zero.\n";

        }

        else throw;

    }

}

As they say, nice try but no cigar. The function GetExceptionCode is implemented as an intrinsic function and can only be called as part of the filter expression in a C __except statement. It seems that some other mechanism is necessary to differentiate between C exceptions in C++ code.

There is yet another possible solution. We could create a C exception handler to catch all C exceptions and throw a C++ exception of type unsigned int with the value of the C exception code. An example program for this is shown in Listing 17.10.

    Listing 17.10. Raising C++ exceptions in a C exception filter.
#include <windows.h>

#include <iostream.h>

int divide(int x, int y)

{

    try

    {

        x = x / y;

    }

    catch(unsigned int e)

    {

        cout << "Inside C++ exception.\n";

        if (e == EXCEPTION_INT_DIVIDE_BY_ZERO)

        {

            cout << "Division by zero.\n";

        }

        else throw;

    }

    return x;

}

unsigned int catchall(unsigned int code)

{

    cout << "inside catchall: " << code << '\n';

    if (code != 0xE06D7363) throw (unsigned int)code;

    return EXCEPTION_CONTINUE_SEARCH;

}

void main(void)

{

    int x, y;

    __try

    {

        x = 10;

        y = 0;

        x = divide(x, y);

    }

    __except(catchall(GetExceptionCode())) {}

}

This approach has but one problem. When the catchall function throws a C++ exception that is not handled by a C++ exception handler, it is treated as yet another C exception, resulting in another call to catchall. This would go on forever, were it not for the test for the value 0xE06D7363, which appears to be a magic value associated with C++ exceptions. But we are getting into seriously undocumented stuff here; there has to be another solution!

At this point, you might ask the obvious question: if C++ programs can use the Microsoft C exception handling mechanism, why go through this dance at all? Why not just use __try and __except and get it over with? Indeed, this is a valid solution; however, to improve code portability, you may want to use the C++ exception handling mechanism when possible, and localize and dependence on Microsoft extensions as much as possible.

Translating C Exceptions

Fortunately, the Win32 API provides a function that allows a much more elegant solution for translating a C exception into a C++ exception. The name of the function is _set_se_translator. Using this function, one can finally obtain an elegant, satisfactory solution for translating C exceptions to C++ exceptions. An example for this is shown in Listing 17.11.

    Listing 17.11. Using _set_se_translator to translate C exceptions.
#include <windows.h>

#include <iostream.h>

#include <eh.h>

int divide(int x, int y)

{

    try

    {

        x = x / y;

    }

    catch(unsigned int e)

    {

        cout << "Inside C++ exception.\n";

        if (e == EXCEPTION_INT_DIVIDE_BY_ZERO)

        {

            cout << "Division by zero.\n";

        }

        else throw;

    }

    return x;

}

void se_translator(unsigned int e, _EXCEPTION_POINTERS* p)

{

    throw (unsigned int) ;

}

void main(void)

{

    int x, y;

    _set_se_translator(se_translator);

    x = 10;

    y = 0;

    x = divide(x, y);

}

Summary

Win32 programmers using the C++ language must face two separate, only partially compatible exception handling mechanisms. Win32-structured exceptions are often generated by the operating system. These exceptions are not dependent on any language-specific implementation and are used to communicate a condition to the application's exception handler using a 32-bit unsigned value.

In contrast, C++ exceptions are typed expressions; the nature of the exception is often derived from the type of the object that is used when the expression is thrown.

C programs can use the __try and __except keywords (which are Microsoft extensions to the C language) to handle structured exceptions. It is possible for exception handlers to be nested. The type of the expression is obtained by calling the GetExceptionCode function in the __except filter expression. Depending on the value of the filter expression, an exception may be handled by the exception handler, execution may continue at the point where the exception occurred, or control can be transferred to the next exception handler. An unhandled exception causes an application error.

C programs can also use termination handlers. These handlers, installed using the __try and __finally keywords, can ensure that a function which is abnormally terminated by an exception is given a chance to perform cleanup.

C++ programs use the C++ try and catch keywords to handle exceptions. The type of the exception is declared following the catch keyword. The catch keyword with an ellipsis declaration (...) can be used to catch all exceptions; one possible use of this construct is to act as a termination handler, analogous to the __finally block in C exception handling.

As C++ programs can also use C exceptions, it is possible to mix the two exception handling mechanisms. C++ programs can catch C exceptions using an ellipsis handler. Unfortunately, this method does not allow C++ programs to obtain the exception code. However, C++ programs can install an exception translator function, which can be used to translate C structured exceptions into C++ typed exceptions.

Previous Page Main Page Next Page