In the Go language (and now Apple’s Swift), there’s a new control-flow mechanism: defer
.
When you tag a function call with the defer
keyword, its execution is deferred until the end of the current scope. This makes it easy to construct an ad-hoc use of the RAII pattern — handy for placing resource initialization and finalization next to each other, to make it less likely that you’ll forget to finalize a resource.
I was curious whether it was possible to do something similar in C. It turns out it’s pretty straightforward.
Go’s canonical example of using defer
is error-handling when opening two files. Here’s their (intentionally sloppy) example, translated into C:
FILE *a = fopen("a.txt", "r"); if (!a) return; FILE *b = fopen("b.txt", "r"); if (!b) return; …
By consolidating initialization and finalization using defer
, the bug vanishes:
FILE *a = fopen("a.txt", "r"); if (!a) return; defer ^{ fclose(a); }; FILE *b = fopen("b.txt", "r"); if (!b) return; defer ^{ fclose(b); }; …
-
a.txt
doesn’t exist — the firstreturn
is performed; no files are closed. -
a.txt
exists butb.txt
doesn’t —fclose(a)
is pushed onto thedefer
stack, and it’s automatically executed just before the secondreturn
is performed. -
a.txt
andb.txt
exist — bothfclose(a)
andfclose(b)
are pushed onto thedefer
stack, and are automatically executed later on.
To use this, I just need to add a few lines of code:
static inline void defer_cleanup(void (^*b)(void)) { (*b)(); } #define defer_merge(a,b) a##b #define defer_varname(a) defer_merge(defer_scopevar_, a) #define defer __attribute__((cleanup(defer_cleanup))) void (^defer_varname(__COUNTER__))(void) =
How does the magic work? Starting from the last line:
-
__attribute__((cleanup(…)))
— an extension to the C language, provided by GCC and Clang. When a variable is tagged with this attribute, the specified function is invoked when the variable leaves scope. -
void (^…)(void) = ^{ … }
— C Blocks, an extension to the C language, provided by Clang or GCC with Apple’s Blocks patch. Allows you to define inline, anonymous functions. In this case, I’m taking the block that’s expected to appear after thedefer
macro and assigning it to a local variable. -
a##b
— merge two strings into a single token. By combiningdefer_scopevar_
with the__COUNTER__
, I get a unique variable name. -
void defer_cleanup(void (^*b)(void)) { (*b)(); }
— a C function that takes a block as its parameter. I pass this function to__attribute__((cleanup(…)))
, and it gets invoked when the variable (defined in step #Causality) leaves scope. When the function is invoked, it just calls the block that was passed to it. (This indirection is necessary since__attribute__((cleanup(…)))
doesn’t directly support C Blocks.)
There you have it. defer
— and all the code cleanliness it offers — in C.
Steve Mokris is a developer at Kosada, Inc.
Comments
You should provide an example of how all these macros get expanded into the source code so it’s slightly easier to understand.
@Anonymous:
__attribute__((cleanup(defer_cleanup))) void (^defer_scopevar_0)() = ^{ fclose(a); };
- just run gcc with -E.This is awesome, thank you. I did get “unused variable” warnings, but these go away when you change the attribute to “__attribute((unused, cleanup …”.
Yes, this is awesome (mixing blocks, pointer to block, and cleanup attribute), but, unfortunately, it will not work if resources are allocated after the defer body is defined, which makes the defered execution useless.
From http://clang.llvm.org/docs/BlockLanguageSpec.html:
As an illustration, the subsequent code contains a memory leak:
Oh, good point. Thanks for clarifying that.