Monday, June 19, 2017

Go To Statement Considered Helpful (part 1: Exceptions)

The most obvious use of a goto statement in C is for exception handling.  Many people before have noted that this is one good use of gotos.

It's not unusual to have a function with a number of checks for errors. When an error is encountered, the function must release locks and free memory acquired and allocated at the start of the function before returning an error to the caller. The simple way to do this is to have all the errors jump to a label near the end of the function where this is done.  Often this is the same code that a successful exit from the function also executes.  This is certainly better than repeating the same code many different times in the same function.   Newer languages have recognized this and implemented this functionality in the language. In C++, you can use throw/catch. In Java, there is also the try/finally construct that can also handle many of these situations.

In C, to handle exceptions, you will often see code with a goto as below:

    buf=malloc(size);
    if ( !buf ) goto error_exit;
    ...
  error_exit:
    printf("Out of memory\n");
    ...

This code is typical enough that many coders are happy with it like that. But what if we could implement exception handling like C++ in C using macros? Well, we can. Two language tricks make this possible.  First, you can use "if (0)" to create code that can only be reached by a goto. Second, the target of an if statement can be a labeled statement or block.  I'll get back to that in a bit.  Here are the macros:

#define THROW(_name) goto handle_exception_ ## _name;
#define CATCH(_name) if(0) handle_exception_ ## _name:

Now there's no need to complain that C lacks exception handling. This works much like the C++ version, but without the parameter. Also, they are scoped for the whole function, as they aren't connected to a “try{...}” block of statements. See the example below:

buf=malloc(size);
if ( !buf ) THROW(out_of_memory);
...
CATCH(out_of_memory) {
    printf("Out of memory\n");
}
if (buf) free(buf);
return;

Wait a second.  What's going on with those macros?  That's some crazy code that can't be right.

The first trick is to protect the target of the goto in the target of an if(0) statement.  That means the target of the goto is in code that can't be reached without the goto.  This trick reduces some of the spaghetti code issues normally associated with goto statements.

But what about that label after the if(0)?

Well, I'm taking advantage of a surprising glitch in the C language definition.  When I first wrote the code, I was thinking that what I needed for my macro was to be able to write: if(0) label: { statements; } but putting the label in there certainly wouldn't be allowed.  Much to my surprise, it compiled and worked!  I went back and checked the language definition, and found that it is, in fact, legal code.  I can't believe they intended this when defining the language, but as a lucky artifact of how they set up the parsing, it is how they defined it.  (Perhaps it was because case statements in a switch work just like labels.  The reason isn't particularly important here.)  This allows you to macro-ize the CATCH without having any ugliness with the target and braces.  You can follow the CATCH macro with a block of statements, a single statement, or just a semicolon to follow the same code that follows.

By hiding all this in the macro, I've effectively extended the C language to add a missing feature.

No comments:

Post a Comment