For fun I implemented a lightweight exception mechanism for C that allows writing transactional code. This is what it looks like:
int device_create(device_t **odevice)
{
throwbase(-1); // makes this function "throwing" with a default return value of -1.
device_t *device = malloc(sizeof *device);
check(device);
if (!device)
throw; // it will rollback all pending changes, by executing all rollback
// instructions in scope before this.
// from now on if an error occurs, we want to "rollback" and free the device so it
// doesn't leak. rollback lets you write code that does exactly that. the following
// instruction will be automatically executed if any instruction thereafter throws
rollback(free(device));
// check is like assert, but always enabled and if it evaluates to false it throws.
check(subsystem1_init(device->subsystem1));
check(subsystem2_init(device->subsystem2));
...
// all good, set the device and return no-error
*odevice = device;
return 0;
}
This (with optimisation on) compiles to exactly the same assembly you would get by manually copy-pasting the rollback instructions every time you intend to "throw" by returning -1.
The implementation relies on a gcc/clang extension that allows taking the address of a label with &&label
. Without any further do, here is the (rather simple) implementation.
/* preliminary preprocessing utilities */
#define paste1(a, b) a##b
#define paste2(a, b) paste1(a, b)
/* generates a unique label id */
#define _errlabel paste2(_err, __LINE__)
/* makes the function throwing and declares the base return value */
#define throwbase(...)\
void* _eh = &&_errlab;\
if (0) { _errlab: return __VA_ARGS__; }
/* specifies code to be executed if function throws later on */
#define rollback(...)\
{\
void* _ehprev = _eh;\
_eh = &&_errlabel;\
if (0) {_errlabel: __VA_ARGS__; goto *_ehprev; }\
}
/* rollbacks changes (by executing previous rollback statements)
and returns error value declared in throwbase */
#define throw goto *_eh
/* evaluates condition and throws if false */
#define check(condition) if (!(condition)) throw
Note: this code does not support nested scopes. It is