March 96 - Using C++ Exceptions in C
Using C++ Exceptions in C
Avi Rappoport
Exception handling in C++ offers many advantages over error handling in C. Using the
techniques outlined here, you can implement C++ exceptions in your C code without a
lot of effort. The payback is streamlined debugging that can result in more error-free
code. When your program encounters errors, it jumps to the appropriate
error-handling section, rather than dealing with the error locally. This simplifies
your design and helps you concentrate on the normal flow of control. Centralized error
handling also makes it easier to improve your reporting and feedback mechanisms
incrementally.
I wrote a few little XCMDs in C and after the fifteenth crash of the day, I decided that I'd
better add some error handling. So I looked at Dartmouth XCMDs, but I wasn't
impressed. Each check for an error meant another indentation in the code, and I was
worried about disposing of handles correctly as I passed errors up the call chain. Since
I'd been looking at a lot of C++ lately, I wondered whether I couldn't use part of the
C++ exception-handling mechanism to avoid problems in my code. It worked pretty
well, so I thought I'd share my results.
For part of my solution, I used some Metrowerks macros. Metrowerks has graciously
allowed these helpful exception and debugging source, header, and resource files to be
included on this issue's CD, so you can use them without purchasing its CodeWarrior
CD. The files contain macros that provide convenient tools for implementing exceptions
and debugging signals, as well as an alert resource that can provide information during
debugging.
Although I've used C++ exception handling in my C code with great results, I'd like to
offer you one word of caution before you use them. Realize that C++ is not strictly an
extension of C; as a result, in some cases it's possible that the program may not behave
as you think it should.
BASIC ERROR-HANDLING REQUIREMENTS
All programs must respond to system and subroutine failures somehow. For example,
many Macintosh Toolbox routines return a variable of type OSErr, while others
require that you call Toolbox routines (such as MemError and ResError) to retrieve
the error. If you ignore system and subroutine failures, your program is practically
guaranteed to crash.
Good error handling allows you to cope with many kinds of problems. Your checks can
trigger other code that deals with the problem (for example, by freeing memory).
During debugging, error checking should notify you that something has gone wrong.
And since you can't, unfortunately, catch all the bugs during testing, you must also set
up an error-reporting mechanism to notify your users when something has gone
wrong. In the worst case, your error handling should at least ensure that your
program exits gracefully, without losing or corrupting user data.
THROWING EXCEPTIONS
The American National Standards Institute (ANSI) has defined a mechanism for C++
compilers that allows code to "throw" exceptions. When the compiler encounters a
throw statement, it jumps to the nearest catch statement. (The "nearest" catch
statement is the one associated with the current try statement, whether it's in the
current routine or farther up the call chain.) The catchstatement can deal with the
error, pass it up the call chain, or both. A throwstatement should appear only within
a try or catch statement or in code called from within a try statement. Listing 1
shows these basic components.
Listing 1. Throwing exceptions
OSErr theErr = noErr;
// Try block.
try {
// Do something.
...
// If error, throw an exception.
if (theErr != noErr)
throw (theErr);
}
// Catch blocks.
catch (OSErr theErr) {
// Do something with the error.
...
}
catch (...) {
// Catch anything else.
...
}
As shown in Listing 1, exceptions are dealt with in catch blocks, which take an
appropriate action depending on the error. For serious errors, this means cleaning up
and terminating the program. For less serious errors, the catch block could continue
without making a fuss, or make changes based on the error and again call the routine
that threw the error; sometimes you might want to throw a more generic error, which
is caught and interpreted in a higher-level catch block. I also recommend using the
Metrowerks signal macros (described later) within your catch blocks to help you
locate errors during debugging.
The three dots in catch (...) are actually in the code; the other such dots
that appear in these listings are ellipses representing code that isn't shown.*
When carefully designed, C++ exception handling in your program can deal with
problems at an appropriate level. As you may already have guessed, this feature is
both powerful and dangerous. The advantage is that you don't have to mess around with
returning errors for every routine or indenting deeply. However, if you allocate
memory, you must be careful to dispose of it at the right time or it will cause a leak.
ADDING C++ EXCEPTIONS TO YOUR CODE
To add C++ exceptions to your code, you must do the following:
• Force the use of the C++ compiler.
• Create a top-level exception handler in your main routine.
• Define try blocks and catch blocks, and call throw at appropriate times.
• Add the C++ library (CPlusPlus.lib, CPlusPlusA4.lib, or
MWCRuntime.Lib) to your project.
The Metrowerks macros that you'll see in the code that follows make implementing
exception handling much easier than it would be otherwise. I'll talk about them later.
USING C++
To use C++ exceptions, you have to force the use of the C++ compiler. In Metrowerks
CodeWarrior, the easiest way is to select the Activate C++ Compiler checkbox in the
C/C++ Language panel. You should also make sure that the Enable C++ Exceptions
checkbox is selected, because it enables throwing exceptions rather than direct
destruction (one of those weird C++ things). An alternative way to invoke the
compiler is to change the extension on your source code files to ".cp" or by changing
the Target panel preferences; however, the checkbox method is the easiest.
C++ is stricter about automatic parameter conversion than C, so selecting the MPW
Pointer Type Rules checkbox in the C/C++ Language panel avoids a bunch of errors (it
forces the compiler to allow some implicit char* casts). But you'll get errors for
other parameters and return values, so you have to clean them up as indicated by the
compiler. For example, the following is an error message returned by a C++
compiler:
HC2RTF.c line 224 textLen = strlen(textString);
Error : cannot convert
'unsigned char *' to
'char *'
To fix this problem, you can change the code to
textLen = strlen((char *) textString)
The CodeWarrior C++ compiler puts special C++ information into function names
(this is called name mangling). C doesn't do this, so header files for C functions should
be surrounded by #extern "C" statements to tell the compiler not to mangle these
names (see Listing 2). The Macintosh Toolbox header files take care of this already.
Listing 2. Preventing name mangling
#ifdef __cplusplus
extern "C" {
#endif
long FindBreak(char* buffer, short len);