Arbitrarily Nestable Defer Statement in C
Defer loops are a common C macro trick:
#define DEFER_LOOP(begin, end) for (int8_t _deferloop_latch_ = ((begin), 0); !_deferloop_latch_; _deferloop_latch_ = 1, (end))
// e.g.
DEFER_LOOP(mutex_lock(&mtx), mutex_unlock(&mtx))
{
// critical section
}
It's simply a loop whose body is run only once, that takes advantage of the
somewhat bizarre
semantics of C's comma
operator to execute begin
before the loop body, and end
after.
It's plausible that you may want to nest defer loops, especially if you've built other useful utilities on top of them:
#define MUTEX_SCOPE(mtx) DEFER_LOOP(mutex_lock(&(mtx)), mutex_unlock(&(mtx)))
#define PROFILE_SCOPE() DEFER_LOOP(profile_begin_section(), profile_end_section())
MUTEX_SCOPE(mtx)
{
// critical section
PROFILE_SCOPE()
{
// profiled section
}
// ...
}
However, there's a problem: when this is expanded, both defer loops will declare the loop counter variable, and the inner variable will shadow the outer variable. Depending on your compiler and build process, this may be a warning or error.
If shadowing isn't a problem in your context, then this is fine. That said, you may want to use this construct in multiple places, libraries, etc. In these environments, making consumers have to deal with warnings from your code adds friction,1 and can possibly cause more serious problems.2
A fairly obvious approach to removing the shadowing problem is simply generating a unique identifier for the loop
counter, which can be done with the
__COUNTER__
macro:
#define GLUE_(A,B) A##B
#define GLUE(A,B) GLUE_(A,B)
#define UNIQUE_IDENT(ID) GLUE(ID, __COUNTER__)
#define DEFER_LOOP_(begin, end, latch) for(int8_t latch = ((begin), 0); !latch; latch = 1, (end))
#define DEFER_LOOP(begin, end) DEFER_LOOP_((begin), (end), UNIQUE_IDENT(_deferloop_latch_))
Note that the GLUE
macros are just a trick to generate an identifier by
concatenating the passed in strings.
Unfortunately, __COUNTER__
isn't standardized, though it is widely
supported.
If being non-standard isn't acceptable, then another approach is needed.
Enter the thread-local defer loop latch:
// NOTE: some variant of thread local is supported on each compiler, I just
// #define thread_local to whatever it is on a given compiler.
//
// There are standardized versions of thread_local, find out what works in the
// standard you're using if that's preferred.
_Thread_local int8_t _t_deferloop_latch_;
#define DEFER_LOOP(begin, end) for (_t_deferloop_latch_ = ((begin), 0); !_t_deferloop_latch_; _t_deferloop_latch_ = 1, (end))
This works because each loop writes to _t_deferloop_latch_
immediately
before the loop test, meaning that any nested our outer loops cannot affect
the current loop's execution path.
I found this trick in Nic Barker's excellent UI layout library, clay.3
There is one caveat: the loop can't be entirely unrolled and ignored by an optimizer. I think this is because reads/writes to a global/thread-local variable cannot be ignored. With the usual approach, the loop latch is a local variable who's entire lifetime and usage is easily visible to the compiler, so the information that the loop can only ever run once (and therefore the loop latch is redundant) is readily available.
Here's a comparison of the two approaches in godbolt.
So what should you use? If the non-standard nature of __COUNTER__
is
acceptable, use the unique identifier approach. Else use
the thread local latch approach.
1. Turning warnings on and off for given sections of c code is different for each compiler and a huge hassle.
2. e.g. turning off warnings when
#include
-ing your code, and forgetting to turn them back on again.3. The
thread_local
touch is my own. To the best of my knowledge, clay isn't meant to be used from multiple threads.