Skip to content

Instantly share code, notes, and snippets.

@keebus
Last active March 25, 2022 21:32
Show Gist options
  • Save keebus/f62efc0d1abb0bacdee814633848ce60 to your computer and use it in GitHub Desktop.
Save keebus/f62efc0d1abb0bacdee814633848ce60 to your computer and use it in GitHub Desktop.
Exception-like code in C (gcc/clang)

Lightweight exceptions in C (with gcc/clang)

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment