Recently, I found the cleanup(func) construct in C. This is an attribute that can be set on variables, and means that when the variable leaves its current scope, the given function is called. It is therefore somewhat similar to a destructor, even though it is set on variables and not on types.
My C functions currently often have the following structure.
My C functions currently often have the following structure.
T1 func1(T2 arg1, ...) { T3* data = T3_create_data(arg1, ...); // step 1 // process 'data' in some way... // step 2 T1 result = data->result; // step 3 T3_release(data); // step 4 return result; // step 5 }
As I write steps 1 and 4 first when creating a new function, I basically never have any memory leaks. However, step 3 is annoying, as I must repeat type name T1. Also, if the processing must be terminated at some point during step 2, the code still needs to reach step 4 so the resources used by 'data' can be released. This requires either a "goto theEnd;" and a label at the beginning of step 3 or 4, or an ever increasing number of indentations and more complex logic. Neither one is particularly pretty. It typically also forces 'result' to be non-const, which is annoying.
By using cleanup(), the code becomes clearer, and the temporary variable in step 3 can be removed.
T1 func2(T2 arg1, ...) { T3* attribute((cleanup(T3_release))) data = // step 4 T3_create_data(arg1, ...); // step 1 // process 'data' in some way... // step 2 return data->result; // step 3+5 }
When browsing through the code it's indeed a bit more difficult to find the 'T3_release' here. Still, the function that allocates data and the one that releases it are mentioned right next to each other. Also, the rest of the code is free to return from the function at any point.
A proper destructor set on the type would handle the case when 'data' is a variable on the stack, and therefore absolutely will die at the end of the block. However, the cleanup() construct also handles variables on the heap, and makes it obvious whether the data is temporary or will stay alive referenced by some other (global) variable. Potentially this could also be used by the compiler to allocate this data in a special section of the heap, for reduced memory fragmentation and/or increased speed.
In my code, sometimes step 1 takes a lock, which is then released in step 4. With some preprocessing magic I can now define the convenient block_lock() macro, as below. By using __COUNTER__ the same block can be protected by multiple locks (which of course must always be taken in the same order) without any problems.
#define mutex_merge(a,b) a##b #define mutex_varname(a) mutex_merge(mutex_tmpvar_,a) #define block_lock(lock) \ mutex_t* __attribute((cleanup(mutex_unlock_ptr))) mutex_varname(__COUNTER__) = lock; \ mutex_lock(lock)
In the function pattern above, steps 1+4 now simply become "block_lock(lockVariable);", and then the rest of the block is protected from other threads. A fun exception is when step 5 actually does something more than just a return, and that something must run without the lock already being taken.
Update: There are at least two important situations to watch out for when using this construct. First, when the function is still using goto. Using goto is fine as such, you just cannot jump over the declaration of a variable that uses the cleanup() attribute. The result is that the variable will never be initialized, but the cleanup code will still be run when the block ends.
The second case is when a variable is declared within a switch statement. If this is between a pair of curly braces, fine. Otherwise the cleanup code will always be run when the switch statement ends, regardless of where it entered.