in band sentinel

Unlike other languages which have one preferred means of signalling an error, C is a multi error paradigm language. Error handling styles in C can be organized into one of several distinct styles, such as popular or correct. Some examples of each.

One very popular option is the classic unix style. -1 is returned to indicate an error.

fd = open("file", O_RDONLY); if (fd == -1) abort();

Another option seen in the standard C library is NULL for errors.

fp = fopen("file", "r"); if (fp == NULL) abort(); if (!(ptr = malloc(size))) abort();

The latter has the advantage that NULL is a false value, which makes it easier to write logical conditions. File descriptor 0 is valid (stdin) but false, while -1 is invalid but true.

And of course, there’s the worst of both worlds approach requiring a special sentinel that you’ll probably forget to use.

ptr = mmap(NULL, size, prot, MAP_ANON, -1, 0); if (ptr == MAP_FAILED) abort();

errno

Other unix functions, those that don’t need to return a file descriptor, stick to just 0 and -1.

if (listen(s, 10) != 0) abort();

Of course, none of these functions reveal anything about the nature of the error. For that, you need to consult the errno on the side.

if (fstat("file", &sb) == -1) { if (errno == EPERM) error("Sorry Dave"); else abort(); }

Other functions take another approach and don’t have any error return at all, instead requiring various degrees of tea leaf introspection.

errno = 0; lval = strtol(buf, &ep, 10); if (buf[0] == '\0' || *ep != '\0') goto not_a_number; if (errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) goto out_of_range;

error returns

Later functions started using a dedicated error return that encoded what went wrong right there in the error.

error = getaddrinfo("host", "port", &hints, &answer); if (error) printf("It all went downhill at %s

", gai_strerror(error));

Or somewhat more simply in the new posix functions.

if (posix_memalign(&ptr, align, size)) abort();

This works pretty well and it’s generally hard to screw it up.



value return

We can also flip the error and value returns of such functions.

val = strtonum(buf, min, max, &errstr); if (errstr) abort();

This is necessary to make the function work with integers of all sizes without resorting to a circus of strtoshort, strtoint, strtolong, strtouint32_t, etc. functions. (More than 20 years after the introduction of LP64 architectures, it’s still possible to find code that does fn((int *)&longval) incorrectly.)



context

Some libraries use a more complicated approach, returning a simple error code and stashing the error reason in a context.

if (tls_connect(ctx, host, port)) printf("error %s

", tls_error(ctx));

Also used in sqlite.

if (sqlite3_step(stmt) != SQLITE_ROW) printf("failure: %s

", sqlite3_errmsg(db));

callback

Moving on to some more complicated libraries that use callbacks.

Xlib requires setting an error handler if you don’t like the default.

XSetErrorHandler(sadtimes);

The default handler is called reallysadtimes and immediately exits the program after any error. This is unfortunate if you’re doing something like selecting SubstructureRedirectMask for which it’s impossible to predict failure, necessitating the custom error handler. The error handler is allowed to return, however, and Xlib will simply carry on and report failure to the caller as well.

The image libraries png and jpeg both use callbacks, but their error handlers are not allowed to return, somewhat complicating the calling procedure.

struct jpeg_compress_struct cinfo; struct { struct jpeg_error_mgr mgr; jmp_buf env; } jerr; cinfo.err = jpeg_std_error(&jerr.mgr); jerr.mgr.error_exit = jpgerr_handler; if (setjmp(jerr.env)) abort(); cinfo.err = jpeg_std_error(&jerr.mgr); jerr.mgr.error_exit = errhandler jpeg_create_compress(&cinfo);

Where errhandler is something that had better call longjmp or it’s going to be a long day.



fin

There’s plenty more, often some combination of the above.