In this Modern C video there's a trick that allows to postpone execution of a code until the block/scope exits. It's used as follows:
int main()
{
int foo=0, bar;
const char *etc = "Some code before defer";
defer(profile_begin(), profile_end())
{
/* Some code, which will be automatically
* preceded by call to profile_begin() and
* followed by run of profile_end().*/
foo++;
bar = 1;
}
etc = "Some code after defer";
foo = bar + 1;
}
Implementation from the video:
#define macro_var_line(name) concat(name, __LINE__)
#define defer(start,end) for( \
int macro_var_line(done) = (start,0); \
!macro_var_line(done); \
(macro_var_line(done) += 1), end)
It's pretty simply implemented. What might be confusing is the macro_var_line(name)
macro. Its purpose is to simply ensure that a temporary variable will have a unique, "obfuscated" name by adding current line number to it (of where defer is called).
However the problem is that one cannot pass code to start
snippet that declares new variables, because it is pasted in the for() comma operator that uses int
type (the int macro_var_line(done) = …
). So it's not possible to, eg.:
defer(FILE *f = fopen("log.txt","a+"), fclose(f))
{
fprintf(f,"Some message, f=%p",f);
}
I would want to have such macro, capable of declaring new vars in start
snippet. Is it achievable with standard C99, C11 or maybe some GCC extensions?
UPDATE: I've found a solution utilizing GCC nested functions. Basically, the { bblock }
that's following the defer()
macro becomes nested function body. And it's possible to forward declare the nested function and invoke it from before the block, i.e.:
#define defer(start,end) \
auto void var_line(routine) (void); \
start; \
/* Invoke above predeclared void routine_123(void) function */ \
var_line(routine)(); \
end; \
/* Define the nested function */ \
void var_line(routine) (void)
UPDATE2: Here's an elegant version which:
- runs first leading statements as start and the last one as the end code,
- runs the very first statement in its own
for()
/declarative space, - runs the block properly via an
if(cond == 0)
check/block start up.
#define defer(...) \
for (int var_line(cond) = 0; var_line(cond) == 0; ) \
for (FIRST_ARG(__VA_ARGS__); var_line(cond) == 0; ) \
for (SKIP_LAST_ARG(SKIP_FIRST_ARG(__VA_ARGS__)); \
var_line(cond) == 0; \
var_line(cond) += 1 ) \
for (int var_line(cond_int) = 0; \
var_line(cond_int) <= 1; \
var_line(cond_int) += 1 ) \
if (var_line(cond_int) == 1) \
{ \
LAST_ARG(__VA_ARGS__); \
} else if (var_line(cond_int) == 0)
As I expressed in comments, my recommendation is to avoid using such a thing in the first place. Whatever your video might have said or implied, the prevailing opinion among modern C programmers is that macro usage should be minimized. Variable-like macros should generally represent context-independent constant values, and function-like macros are usually better implemented as actual functions. That's not to say that all macro use must be avoided, but most modern C professionals look poorly on complex macros, and your
defer()
is complex enough to qualify.Additionally, you do yourself no favors by trying to import the style and idioms of other languages into C. The common idioms of each language become established because they work well for that language, not, generally, because they have inherent intrinsic value. I advise you to learn C and the idioms that C programmers use, as opposed to how to write C code that looks like Go.
With that said, let's consider your
defer()
macro. You write,, but in fact the restriction is stronger than that. Because the macro uses the
start
argument in a comma expression (start,0
), it needs to be an expression itself. Declarations or complete statements of any kind are not allowed. That's only indirectly related to that expression appearing in the first clause of afor
statement's control block. (The same applies to theend
argument, too.)It may also be important to note that the macro expands to code that fails evaluate the
end
expression if execution of the associated statement terminates by branching out of the block via areturn
orgoto
statement, or by executing a function that does not return, such asexit()
orlongjmp()
. Additionally, unlike with Go'sdefer
, theend
expression is evaluated in full after the provided statement -- no part of it is evaluated before, which might surprise a Go programmer. These are characteristics of the options presented below, too.If you want to pass only the
start
andend
as macro arguments, and you want to allow declarations to appear instart
, then you could do this:That moves
start
out of thefor
statement in the macro's replacement text, to a position where arbitrary C code may appear. Do note, however, that any variable declarations will then be scoped to the innermost containing block.If you want to limit the scope of your declarations then there is also this alternative and variations on it, which I find much more straightforward than the original:
You would use that like so:
That is somewhat tuned to your particular example, in that it assumes that the body is given as a sequence of zero or more complete statements (which can include blocks, flow-control statements, etc). As you can see, it also requires the body to be passed as a macro argument instead of appearing after the macro invocation, but I consider that an advantage, because it facilitates recognizing the point where the deferred code kicks in.