Thursday, September 30, 2010

RAII in C, too

I've been using a macro to encapsulate exactly what is going on in this "RAII in C" link, which abstracts out the more boilerplate aspects of writing code like this and leaving behind something far more clean and direct:

(Lazy, i.e. non-compiling, near-C code...)

// lispy macros
#define CAR(first,rest...) first
#define CDR(first,rest...) rest

// TRY macro easing the implementation of the RAII paradigm in C
// This implementation just scratches the surface...
#define TRY(_cond,_status,_exitpoint,_msg...)
{
if (TRY_trace) printf(#_cond ": " CAR(_msg) "\n",CDR(_msg));
if (_cond)
{
status=(_status);
printf("Failed" CAR(_msg) ", status=%d\n",CDR(_msg),status);
goto _exitpoint;
}
}

where:

"cond" is any expression that returns anything that can be interpreted as a boolean (i.e. "if (cond)..."),
"status" identifies the error if "cond" evaluates to non-zero/true,
"exitpoint" refers to a code label which will be goto'ed if "cond" is non-zero/true, and
"msg" documents what "cond" is supposed to do.

Example usage:

int foo(int bufsize,char *filename)
{
int status=0;
char *buf;
FILE *file;

TRY(!(buf=malloc(bufsize)), -1, done, "allocating %d byte buf",bufsize);
TRY(!(file=fopen(filename,"r")), -1, deallocate_buf, "opening file %s",filename);
TRY(status=bar(buf,file), status, close_file, "bar'ing buf[%d], file %s",bufsize,filename);

// could "goto done" here if not releasing resources immediately.

close_file:
fclose(file);
deallocate_buf:
free(buf);
done:
return status;
}

Niceties:
* RAII-like* paradigm, in a very compact and concise form
* Focus on what's important/unique, forget about boilerplate
* Every important line's purpose is auto-documented, as a side-effect of design

But most importantly, every important line of code is wrapped in a macro(!). An entire application can be altered, instrumented, re-interpreted, or otherwise arbitrarily processed simply by changing the definition of TRY. Trivial examples would be changing how the messages are printed/logged, or only "tracing" TRY calls to a certain depth. A less-trivial example would be implementing a debugging feature that allowed you single-step through TRY-instrumented code on-demand.

*I've never called this RAII myself, although it does make dealing with resource allocation/release consistently very easy. It's more of a general control-flow method I use; a controlled way of using goto in a way that makes nested, vulnerable-to-failure resource allocations easy to control and clean up.