mikeash.com: just this guy, you know?

Posted at 2012-08-24 13:15 | RSS feed (Full text feed) | Blog Index
Next article: Friday Q&A 2012-08-31: Obtaining and Interpreting Image Data
Previous article: Friday Q&A 2012-08-10: A Tour of CommonCrypto
Tags: c evil fridayqna guest linux preprocessor
Friday Q&A 2012-08-24: Things You Never Wanted To Know About C
by Gwynne Raskind  

It's been a bit since I did an article, but I'm back again, with a somewhat off-the-cuff treatment of a very twisted set of code I use to pretend that C has exceptions. I delve into little-known extensions of C, Linux compatibility, and worst of all, goto, so be warned!


Error handling in C
C, being something of a bare-metal language by today's standards, lacks any advanced facilities for managing control flow when errors happen. Most platforms have found their own ways of coping with the situation, some with language features, some with framework conventions, and others by not coping at all.

Exceptions
The best known, and also perhaps most debated, means of handling control flow for error conditions is exceptions. C++, Java, Objective-C, PHP, and many others all have some form of exception handling construct. In Objective-C, exceptions look like this:

    @try {
        if (![someObject doSomethingInteresting]) {
            @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Failed to frobulate the glockenspiel!" userInfo:nil];
        }
        return YES;
    } @catch (NSException *exception) {
        NSLog(@"Either die in the vacuum of space, or tell me what you thought of my poem.");
        return NO;
    } @finally {
        [prognosticator cleanupAfterSlithyToves];
    }

Fortunately, Apple realized how ridiculous this tends to look in Cocoa and deprecated exceptions for error handling, instead opting for the NSError model. The use of exception handling in Objective-C is now reserved (at least in theory) for truly "exceptional" conditions, particularly programmer error.

C does have a primitive sort of exception handling in the setjmp/longjmp functions. However, they're extremely weird, not very full featured, and highly error-prone, so they make a poor choice.

Historically, exceptions in Objective-C were implemented with these functions, but Apple quite sensibly replaced the unwieldy macros with a compiler feature which calls into the C++ exception implementation. In essence, @try in Objective-C and try in C++ are now the same thing, though they're still not interchangeable due to the separation of class heirarchies.

Grafting exceptions onto C
The first question asked is why anyone would want to put exceptions into pure C. My answer is that this code is just too ugly to let live:

    struct whatever *make_a_whatever(int alpha, const char *beta, const char *gamma, size_t delta)
    {
        struct whatever *brillig = malloc(sizeof(struct whatever));
        int fd = -1, r = 0;

        if (!brillig)
            return NULL;
        brillig->alpha = alpha;
        brillig->beta = strdup(beta);
        if (!brillig->beta) {
            free(brillig);
            return NULL;
        }
        if ((fd = open(gamma, O_RDONLY)) == -1) {
            free(brillig->beta);
            free(brillig);
            return NULL;
        }
        brillig->gamma = malloc(delta);
        if (!brillig->gamma) {
            close(fd);
            free(brillig->beta);
            free(brillig);
            return NULL;
        }
        if ((r = read(fd, brillig, delta)) != delta) {
            close(fd);
            free(brillig->gamma);
            free(brillig->beta);
            free(brillig);
            return NULL;
        }
        close(fd);
        return brillig;
    }

Admittedly, the example is contrived and could be reorganized to require less repetition, but in the real world I've ended up with code that looks a lot like this. One possible solution is a dumb goto:

    struct whatever *make_a_whatever(int alpha, const char *beta, const char *gamma, size_t delta)
    {
        struct whatever *brillig = malloc(sizeof(struct whatever));
        int fd = -1, r = 0;

        if (!brillig) goto error;
        brillig->alpha = alpha;
        if (!(brillig->beta = strdup(beta))) goto error;
        if ((fd = open(gamma, O_RDONLY)) == -1) goto error;
        if (!(brillig->gamma = malloc(delta))) goto error;
        if ((r = read(fd, brillig, delta)) != delta) goto error;
        close(fd);
        return brillig;
    error:
        if (fd != -1) close(fd);
        if (brillig) {
            free(brillig->gamma);
            free(brillig->beta);
            free(brillig);
        }
        return NULL;
    }

This is certainly better, but still litters the code with a nest of tightly clustered if branches. The next step to readability is to macroize the branches:

    #define error_if(c)     do { if (!(c)) goto error; }
    #define error_ifnull(p) do { if ((p) == NULL) goto error; }
    #define error_ifneg1(e) do { if ((e) == -1) goto error; }

    struct whatever *make_a_whatever(int alpha, const char *beta, const char *gamma, size_t delta)
    {
        struct whatever *brillig = malloc(sizeof(struct whatever));
        int fd = -1;

        error_ifnull(brillig);
        brillig->alpha = alpha;
        error_ifnull(brillig->beta = strdup(beta));
        error_ifneg1(fd = open(gamma, O_RDONLY));
        error_ifnull(brillig->gamma = malloc(delta));
        error_if(read(fd, brillig, delta) == delta);
        close(fd);
        return brillig;
    error:
        if (fd != -1) close(fd);
        if (brillig) {
            free(brillig->gamma);
            free(brillig->beta);
            free(brillig);
        }
        return NULL;
    }

Now, what if you want to know what went wrong? You have no exceptions to pass information up the call stack, nor any place to stuff an error message besides printing to stderr, which may not be appropriate for an internal function. What if you have significant cleanup that has to be done regardless of whether or not errors occurred? What if you want to be able to return early from the function but have your cleanup code still run?

The Solution: Preprocessor Abuse 101
For the sake of brevity, I'll skip a few steps in the process that leads to the solution I came up with for these problems and just show it. I needed error handling that:

That last is a doozy. One might imagine blocks as a fairly simple solution to several of these issues at once, but blocks don't exist in GCC outside of Apple's obsolete customized version.

First, here is what our make_a_whatever function looks like with the error handling I wrote:

    struct whatever *make_a_whatever(int alpha, const char *beta, const char *gamma, size_t delta, error_t **error)
    {
        struct whatever *brillig = NULL:
        int fd = -1;

        error_enter() {
            on_nullerror(brillig = malloc(sizeof(struct whatever)), ENOMEM, "can't allocate our brillig!");
            brillig->alpha = alpha;
            on_nullerror(brillig->beta = strdup(beta), ENOMEM, "can't allocate beta for brilling with alpha %d!", alpha);
            on_errno(fd = open(gamma, O_RDONLY), "can't open gamma %s!", gamma);
            on_nullerror(brillig->gamma = malloc(delta), ENOMEM, "can't allocate gamma either!");
            on_readerr(fd, brillig, delta, "delta's not in gamma!");
            return brillig;
        } error_handler() {
            if (brillig) {
                free(brillig->gamma);
                free(brillig->beta);
                free(brillig);
            }
            return NULL;
        } error_finally() {
            if (fd != -1)
                close(fd);
        } error_exit(error);
    }

Whether or not you find this more readable is a matter of opinion, of course, but it's certainly more functional. For any given error, a formatted message is available, and the error is returned by reference if and only if one occurred. Here's an example call:

    error_t *error = NULL;
    struct whatever *whatever = make_a_whatever(1, "gyre", "gimble", SIZE_WABE, &error);

    if (!whatever)
        error_write(error, STDERR_FILENO, true);

For convenience, the error_write function can optionally free the error_t object (this is the third parameter).

There's a good bit of magic behind these macros and functions. Here they are in order of appearance:

error_enter
Prepare yourself:

    #define error_enter()   do {    \
        __label__ _error_start, _error_exit, _error_finally, _error_done;   \
        __block error_t *_last_error = NULL;    \
        do {    \
            CLEANUP_DECL    \
        _error_start:;

Yuck, huh?

First, the entry macro declares several "local" labels. A local label is simply a label whose name is local to its enclosing scope. This is a GCC extension supported by Clang. These four labels are used by the rest of the macros to manage the function's control flow. Notice that because the labels are used across multiple macros, error handling scopes can not be nested.

Next, an error_t is declared to hold any error that occurs within this error handling scope. It is marked __block, as the Clang implementation of the "finally" clause uses blocks and must update this variable from inside one. For the GCC build, __block is #defined to nothing.

Next, the "cleanup declaration" takes place, then the "start" label is marked. The main function body goes underneath this.

CLEANUP_DECL and friends
The cleanup delcaration and several other pieces of the cleanup implementation are macros, as the implementation is significantly different between GCC and Clang:

    #ifndef __has_feature
    #define __has_feature(f) 0
    #endif

    #if __has_feature(blocks)
    typedef void (^_error_cleanup_block_t)(void);
    static inline void _error_cleanup_block(_error_cleanup_block_t *b) { (*b)(); }
    #define CLEANUP_DECL _error_cleanup_block_t _error_cleanup __attribute__((cleanup(_error_cleanup_block),unused)) = NULL;    \
                         goto _error_finally;
    #define CLEANUP_ASSIGN _error_cleanup = ^
    #define CLEANUP_DONE_ASSIGN goto _error_start
    #elif __GNUC__
    #define __block
    typedef void (*_error_cleanup_func_t)(void);
    #define CLEANUP_DECL auto void _error_cleanup_f(_error_cleanup_func_t *f);  \
                         void (*_error_cleanup)(void) __attribute__((cleanup(_error_cleanup_f),unused)) = NULL;
    #define CLEANUP_ASSIGN  void _error_cleanup_f(_error_cleanup_func_t *f)
    #define CLEANUP_DONE_ASSIGN
    #endif

For Clang (and any potential future GCC which adopts blocks), a typedef is declared for a cleanup block, just your basic takes-and-returns-void. A static inline C function is defined (in the header, remember, not as part of the macro expansion) for running the block.

The cleanup delcaration for Clang declares an _error_cleanup_block_t with the cleanup attribute. This attribute (another extension originally from GCC) says "run this C function when this variable goes out of scope". It is also marked unused to avoid warnings when -Wunused-variable is turned on. Next, it jumps to the "finally" label, before the function's main body runs. As we'll see later, this is necessary to make sure the cleanup block is properly set up before any potential return from the function.

For GCC, which has no blocks, a similar process takes place, but __block is defined to nothing, and instead of declaring a cleanup block, the code declares a "nested function" to serve for cleanup.

Nested functions are a GCC-only extension which Clang refused to adopt as being not worth the trouble when blocks provide a much better solution for scope-specific functions. The nested function exists only within its parent function and has the same ability to capture from the lexical scope of the parent that a block does. As a nested function can not be used outside its parent function, unlike blocks, it has no need for a special qualifier to specify the memory management around captured variables.

Lastly, the GCC version does not jump to the "finally" label, as the nested function does not need to be assigned anywhere to work; the prototype is enough to use it.

The auto keyword, otherwise pretty much useless in C, marks the delcaration as the prototype for a nested function, rather than as a standard function prototype. This is a nonstandard use of a standard keyword and yet another GCC extension.

on_everything
For the sake of brevity, again, I will not paste every single one of the on_*() macros here, as they're all largely the same.

    // Common macros, do not call directly
    #if DEBUG
    #define _error_fail(e, msg, ...)    do {    \
        _last_error = error_newf(e, __LINE__, __FILE__, __PRETTY_FUNCTION__, msg, ## __VA_ARGS__);  \
        goto _error_exit;   \
    } while (0)
    #else
    #define _error_fail(e, msg, ...)    do {    \
        _last_error = error_newf(e, msg, ## __VA_ARGS__);   \
        goto _error_exit;   \
    } while (0)
    #endif
    #define _error_throw() do { goto _error_exit; } while (0)

    // Use e as the error code when (e) < 0.
    // Intended for POSIX routines that return an error code, such as pthread_*()
    #define on_error(e, msg, ...)       do {    \
        int _error_code __attribute__((unused)) = (e);  \
        if (_error_code < 0)    \
            _error_fail(_error_code, msg, ## __VA_ARGS__);  \
    } while (0)

    // Re-throw an error when (e) == false.
    // Intended for chaining error-returning methods.
    #define on_falsethrow(e) do { if (!(e)) _error_throw(); } while (0)

Each on_*() macro checks its own particular condition for an error, saves off any relevant error code for use by the formatting function, and calls the _error_fail() macro.

In turn, the _error_fail() macro sets the _last_error variable to a new error_t based on the passed error code, message, and in debug mode, the file, line, and function in which the error occurred. It then jumps to _error_exit, which takes it to the "catch" handler (see below).

The "throw" versions of these macros are intended for passing errors up the call chain; the existing error_t object is simply left as is and the "catch" handler is invoked directly. For example:

    bool make_many_whatevers(size_t n, struct whatever **list, error_t **error)
    {
        struct whatever *mimsy = NULL;
        size_t i = 0;

        error_enter() {
            for (i = 0; i < n; ++i) {
                on_nullthrow(mimsy = make_a_whatever(/* etc */, error));
            }
            return true;
        } error_handler() {
            for (size_t j = 0; j < i; ++j)
                free_a_whatever(list[i]);
            return false;
        } error_finally() {
        } error_exit(error);
    }

The caller will receive whatever the underlying error was.

error_handler
And now the "catch" block:

    #define error_handler() \
            goto _error_done;   \
        _error_exit: __attribute__((unused));

The error_handler() macro comes immediately before the "something went wrong" part of the function. Immediately after the main body of the function, it jumps to the "done" label. This is the "normal exit" from function case, making the main body look like this:

    _error_start:
        // main body
        goto _error_done;
    _error_exit:
        // something went wrong

The _error_exit label is declared unused so that the compiler doesn't complain if the function has no on_*() statements, which could happen if you're future-proofing a routine for adding error handling later but haven't gotten there yet. This is where the "catch" code goes, handling any errors.

error_finally
And finally, the "finally":

    #define error_finally() \
            goto _error_done;   \
        _error_finally:;    \
            CLEANUP_ASSIGN {

This is where the fun happens. The "finally" part, coming after the "catch", jumps to "done" at the end of it. Notice that this means the "finally" code is never directly executed in normal control flow! In fact, in the GCC version, the lines under _error_finally are never executed at all.

CLEANUP_ASSIGN in Clang takes the _error_cleanup block that was declared in CLEANUP_DECL and actually assigns the "finally" code to it. Hence, any variables whose values need to be modified by the "finally" handler must be declared __block. This is why, as we saw above, the "start" jumps to here before actually executing the function's main body; if the block wasn't assigned at that time, no cleanup would ever be executed.

The GCC version simply starts the nested function which was declared earlier. In either case, the "finally" code ends up as part of a function which is not part of the normal control flow, and will be executed by the cleanup attribute on the _error_cleanup variable. Notice that the GCC version never bothers assigning any value to that variable, since the nested function will simply be called by name instead of jumped to by value as a block would be.

error_exit
Time to clean up after ourselves:

    #define error_exit(error_param) \
                if (_last_error && error_param) \
                    *error_param = _last_error; \
            };  \
            CLEANUP_DONE_ASSIGN;    \
        _error_done:;   \
        } while (0);    \
    } while (0)

As part of the "finally" block, the error parameter which was passed to this macro, if non-NULL, is given the value of _last_error, if any. The "finally" block is ended, and under Clang, control jumps back to the "start", continuing the main body of the function. (Again, under GCC the extra bit of control-flow trickery doesn't need to happen.)

The "done" label, which marks the final end of all this mess, shows up here, and the error handler scope is closed out.

So, here are the steps in execution order:

  1. Declare an error variable and a cleanup block.
  2. In Clang only, jump to the "finally" handler to assign the code to the cleanup block.
  3. In Clang only, jump back to the function's main body.
  4. On error only, assign the error variable, and run the error handler.
  5. Execute the "finally" handler.
  6. Update any error parameter.

The error_*() functions
There's one group of code we haven't looked at yet: The functions which actually manage the error_t type. They're pretty straightforward; there's no hackery involved in this part. Well, maybe just a little to cover the differences between the debug and non-debug cases and grab the executable's name:

    typedef struct {
        int error_code;
        char *message;
    #if DEBUG
        int line;
        char *file;
        char *function;
    #endif
    } error_t;

    #if DEBUG
    #define DEBUG_PARMS , int line, const char *file, const char *function
    #define DEBUG_PASS(f) , line, f(file), f(function)
    #define DEBUG_FNUM 5
    #define DEBUG_VNUM 6
    #else
    #define DEBUG_PARMS
    #define DEBUG_PASS(f)
    #define DEBUG_FNUM 2
    #define DEBUG_VNUM 3
    #endif

    static inline error_t __attribute__((malloc)) *error_new(int error_code DEBUG_PARMS, char *message)
    {
        error_t *r = calloc(1, sizeof(error_t));

        *r = (error_t){ error_code, strdup(message) DEBUG_PASS(strdup) };
        return r;
    }

    static inline error_t __attribute__((format(printf,DEBUG_FNUM,0),malloc)) *error_newvf(int error_code DEBUG_PARMS,
                                                                                           char *format, va_list args)
    {
        error_t *r = calloc(1, sizeof(error_t));

        *r = (error_t){ error_code, NULL DEBUG_PASS(strdup) };
        if (vasprintf(&r->message, format, args) < 0)
            r->message = strdup(format);
        return r;
    }

    static inline error_t __attribute__((format(printf,DEBUG_FNUM,DEBUG_VNUM),malloc)) *error_newf(
        int error_code DEBUG_PARMS, char *format, ...)
    {
        error_t *r = NULL;
        va_list args;

        va_start(args, format);
        r = error_newvf(error_code DEBUG_PASS(), format, args);
        va_end(args);
        return r;
    }

    static inline void error_free(error_t *error)
    {
    #if DEBUG
        free(error->function);
        free(error->file);
    #endif
        free(error->message);
        free(error);
    }

    static inline void error_write(error_t *error, int fd, bool do_free)
    {
    #ifdef MACOSX
        extern char **_NSGetProgname(void);
        char *progname = *_NSGetProgname();
    #elif defined(LINUX)
        char buf[MAXPATHLEN + 1] = {0}, *progname = buf;
        if (readlink("/proc/self/exe", buf, MAXPATHLEN) < 0)
            progname = "unknown";
    #endif
    #if DEBUG
        dprintf(fd, "%s: %s (in %s at %s:%d)\n", basename(progname), error->message, error->function, error->file, error->line);
    #else
        dprintf(fd, "%s: %s\n", basename(progname), error->message);
    #endif
        if (do_free)
            error_free(error);
    }

The only really interesting part of this code is the part in error_write() which grabs the executable's name. Since it's not assumed that argv is available directly, some platform-specific code is invoked. On OS X, the _NSGetProgname() function from crt_externs.h is used, whereas on Linux, the contents of /proc/self/exe symlink are read. dprintf() is just fprintf() that takes a descriptor instead of a FILE *.

For the curious, __attribute__((malloc)) marks the function as returning a pointer that is guaranteed not to be aliased by any other pointers, which is useful for optimization purposes. __attribute__((format(printf,n,m))) simply tells the compiler to do the same checking of the format string and arguments that it would do for the printf() family of functions. These and the other attributes are documented in the GCC Manual.

Conclusion
This error handling trick takes a hybrid exception/error-object approach to the problem, and comes with a laundry list of drawbacks:

This code is hastily designed, organically grown, has obvious inefficiencies, and is definitely not useful in a production environment in this form. Mostly, it's just a cool way of playing with the language. Still, it shows just how much a determined (and perhaps slightly unbalanced) mind can do with relatively crummy tools.

For some even more extreme examples of doing strange and unbelievable things in both C and Objective-C, I recommend looking into Justin Spahr-Summers' libextc and libextobjc libraries, which served as partial inspiration for the code I presented here. Both contain some of the most amazing and arcane uses of macros, switch, if, for, and Duff's Device that I've ever encountered outside of the IOCCC. To top it off, libextc manages to do all of its work while being completely platform- and compiler-agnostic, which is a true accomplishment.

That's about all for this week. Stay tuned for Mike's next article, coming to you a week from today. Thanks for reading!

Did you enjoy this article? I'm selling whole books full of them! Volumes II and III are now out! They're available as ePub, PDF, print, and on iBooks and Kindle. Click here for more information.

Comments:

Congratulations Mike, I had not see C doing things like that. It sounds almost immoral :P.

Jokes aside, great article as usual :).
There is a nice wrapping of setjmp/longjmp for C that gives you a feel of using Try-Catch as you would do in C++, C# or Java.

http://www.nicemice.net/cexcept/
Another possibility is what I call the 'Unix Convention'. Your function returns an integer, with zero for success and errors mapped to different negative numbers. Macros for the error codes make things a little easier to read.

When you have a function that returns or creates something, you pass in a pointer to be populated as the last parameter of the function.

Then you can the function, check the return value, and if it's zero, then your pointer is good.

This is how I did some things with a pre-ANSI C compiler. I think it gets you most of the way there.

Mike,
I am bit confused by your stance.

<block>
Fortunately, Apple realized how ridiculous this tends to look in Cocoa and deprecated exceptions for error handling, instead opting for the NSError model. The use of exception handling in Objective-C is now reserved (at least in theory) for truly "exceptional" conditions, particularly programmer error.
</block>

I have seen this earlier on web and not necessarily in agreement with Apple on Error vs exception using language constructs like (try,catch, finally) for reasons explained by you in this article i.e. usage of error codes, goto statement, unreadable code etc when not using exception.

What is you stand - should we use exceptions in objective C or not ?

I am not clear as in the same article you are liking Apple's recommendation on NSError instead of exception and you are also exampling how to use try-catch.

Most likely I am missing something here.

Would appreciate if you could guide.

Regards & thanks
Kapil





Kapil: My stand is the same as Apple's: Do not use exceptions in Objective-C to manage your control flow. The examples I wrote in this article were purely illustrative, not indicative of a way of doing anything.

In the general sense, this whole discussion was completely moot from an Objective-C point of view; the NSError-as-out-parameter paradigm is well-established in modern Cocoa, and it works. This article presents an approach geared towards pure C without clearly advocating any particular methodology at all (which is one of its problems).
Note that this is another guest article, written by Gwynne, not me.

As for errors versus exceptions, I generally agree with Gwynne here. Exceptions can be used well, but it's tough to do so, and way too much code uses exceptions for expected errors rather than exceptional conditions. Within Cocoa, it's very difficult to use exceptions correctly (they don't cooperate with ARC by default, throwing through framework code is an error, the built-in classes almost always use errors rather than exceptions, etc.) so I don't recommend using them in Cocoa, regardless of their merits in other languages.
Thanks Gwynne and Mike for the clarifications.

<mike>
Within Cocoa, it's very difficult to use exceptions correctly (they don't cooperate with ARC by default, throwing through framework code is an error, the built-in classes almost always use errors rather than exceptions, etc.) so I don't recommend using them in Cocoa, regardless of their merits in other languages.
</mike>

So this is the actual reason for using NSError vs exception and is because of platform limitations from Apple.

My understanding is that if everything was ideal (from platform support point of view) then the usage of exceptions i.e. try-catch-finally constructs vs error codes/NSError is the way to go to have more cleaner & robust code.

I hope that Apple and bloggers would explain it the way you mentioned i.e "in the context of cocoa" it is recommended to use NSError because of "platform limitations". The discussion on the web makes it sound like try-catch-finally is bad vs error codes in general.

I hope we are on the same page else please do guide.

Regards & thanks
Kapil


. In essence, @try in Objective-C and try in C++ are now the same thing, though they're still not interchangeable due to the separation of class heirarchies.


This is only true for Mac 64-bit and iOS. For compatibility, these language constructs still use the old mechanism for 32-bit Mac apps.
Interesting article. I'd like to point out AssertMacros.h containing the require_action etc set of macros. To my eyes, you have pretty much ended up with the same solutions to the same problems that those macros solve.

These macros are ancient. I first came across them in the 1992 Develop magazine mentioned in the file header. Unfortunately I don't seem to have that particular issue anymore. I do have a stack of Develop magazines in the basement ...Purely for "historical" reasons, of course...
I did something similar, using only standard C99:
https://github.com/jspahrsummers/libextc/blob/master/include/exception.h

The macros are even more abhorrent, but enable usage like:


try {
    try {
        raise(Exception, NULL);
    }
} catch (Exception, ex) {
    caught = 1;
} finally {
    assert(caught == 1);
    executed_finally = true;
}
Ha, I missed the closing paragraph of the article. :)
Justin: Yeah, you were the whole inspiration for all this; I just didn't limit myself to standards-compliance :). (Not the best choice on my part, but GCC and Clang do seem to be fairly ubiquitous these days, in my experience.)
Anyone who programs in "C" for anything other than trivial applications, should be shot at dawn.
"C" has cost the planet an incalculable amount in just the HUGE cost of undetected buffer over-runs to render using it a crime against humanity.
If you look at the Microsoft autogenerated code, it does something very similar in its TRY - CATCH macros.

In the early 90s I used to use longjmp for the throw and setjmp for the catch point.
Michael Kingsford Gray: "C" has not ever cost anything to anyone. Buffer overruns are the product either inattentive developers or those who are not as adept at the language as they would like to appear. In the same vein, if you don't know how to use a jackhammer did so anyhow, and created a mess, would you blame the tool?
Michael Kingsford Gray and Gary Hetzel: Brainlessly dogmatic positions are not welcome here, sorry. Both the idea that C should never be used for anything, and the idea that C is perfectly fine and the only problem is bad programmers, are stupid. If you guys want to hold an argument over which ridiculous position is better, feel free, but please hold it somewhere else.
Exceptions can be used well, but it's tough to do so, and way too much code uses exceptions for expected errors rather than exceptional conditions.

Kinda reminds me of a joke I had with some co-workers a few years back about purposely creating overly complex code in java.

Something with the gist of:

void foo()
{
  // ... some code ...

  throw new EverythingIsFineException();
}

try
{
  foo();

  // ... We reached an error ...
}
catch(EverythingIsFineException ex)
{
}


I think the discussion was something relating to a Rube Goldberg like coding contest.
I quite liked the way that the Plan 9 kernel solved this problem with setjmp/longjmp.

For example, go to this page, search for syspipe and check out how the error handling logic works.
http://plan9.bell-labs.com/sources/plan9/sys/src/9/port/sysfile.c

It's error-prone (you can easily forget a poperror) but it's at least clean and quite easy to check mechanically.

Oh, it also has the problem that local variables aren't guaranteed to stay around after a longjmp, so it's probably only a historical curiosity.

I miss Go's defer statement whenever I program in C these days.
Not wanting to nitpick, but shouldn't that be calloc instead of malloc for all exept the first example? Otherwise the returned block for brillig will contain garbage values which will potentially crash in the exception code for free(brillig->gamma).

Interestingly, because of this, despite the verbosity and repetitiveness of the first example, it's the only one that actually safely handles the errors!
Peter: Nice catch! Ironically enough, in real code I always use calloc instead of malloc even in cases where I know for a fact it doesn't matter, just to avoid ever writing myself into corners exactly like that one; I only used malloc for this contrived example because I have a habit of falling back to "beginner" habits for example code.

Joke's on me, though; a beginner would almost certainly be using fopen and fread instead of open and read. I just have an aversion to stdio :).
The nested functions are toxic in the case of no blocks. They are mutually exclusive with no-exec stacks (-Wl,-z,noexecstack).

You can't use no-exec stacks because GCC puts a trampoline on the stack.

The loss of no-exec stacks should be a big problem if an organization (or developer) have an SDLC in place.

Comments RSS feed for this page

Add your thoughts, post a comment:

Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.

Name:
The Answer to the Ultimate Question of Life, the Universe, and Everything?
Comment:
Formatting: <i> <b> <blockquote> <code>.
NOTE: Due to an increase in spam, URLs are forbidden! Please provide search terms or fragment your URLs so they don't look like URLs.
Code syntax highlighting thanks to Pygments.
Hosted at DigitalOcean.