Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it OK to use a code block as an argument for a C macro?

Tags:

I have a pattern that is basically some boilerplate code with a part that varies in the middle

if(condition){
    struct Foo m = start_stuff();
    { m.foo = bar(1,2); m.baz = 17; } //this part varies
    end_stuff();
}

Is it OK to make a macro taht takes that intermediate code block as an argument? The rules for macro expansion in C seem awfully complicated so I am not sure if there aren't any corner cases that could come and bite me in the future (in particular, I don't understand how the macro arguments are separated if my code has commas in it).

#define MY_MACRO(typ, do_stuff) do { \
    if(condition){ \
        struct typ m = start_stuff(); \
        do_stuff; \
        end_stuff(); \
    } \
}while(0)

//usage
MY_MACRO(Foo, {
   m.foo = bar(1,2);
   m.baz = 17;
});

So far the only thing that I managed to think of is break and continue getting captured if I use looping statements in my macro and that would be an acceptable tradeoff for my particular use case.

edit: Of course, I would have used a functions if I could. The example I used in this question is simplified and doesn't showcase the bits that can only work with macro magic.

like image 808
hugomg Avatar asked Jun 19 '13 04:06

hugomg


People also ask

What is macro argument in C?

macro (array[x = y, x + 1]) passes two arguments to macro : array[x = y and x + 1] . If you want to supply array[x = y, x + 1] as an argument, you can write it as array[(x = y, x + 1)] , which is equivalent C code. All arguments to a macro are completely macro-expanded before they are substituted into the macro body.

How many arguments macro can have in C?

For portability, you should not have more than 31 parameters for a macro. The parameter list may end with an ellipsis (…).


2 Answers

You can put a code block into a macro argument provided that it has no unguarded comma. In your example, the only comma in the argument is guarded because it is surrounded by parentheses.

Note that only parentheses guard commas. Brackets ([]) and braces ({}) do not. (And neither do angle brackets (<>) as noted in a comment.)

However, if the code block argument is the macro's last argument, you can use a variadic macro to increase flexibility. But beware: the increased flexibility also means that errors might go unnoticed. If you do this, you'll only have to make sure that the parentheses are balanced. (Again, only parentheses matter to the macro processor.)

like image 75
rici Avatar answered Sep 18 '22 22:09

rici


As an alternative, you could consider using a macro that precedes your compound statement, as illustrated below. One of the pros of this is that all debuggers would still be able to step inside your compound statement, which is not the case with the compound-statement-as-macro-argument method.

//usage
MY_MACRO(Foo, condition) {
   m.foo = bar(1,2);
   m.baz = 17;
}

Using some goto magic (yes, 'goto' may be evil in some cases, but we have few alternatives in C), the macro can be implemented as:

#define CAT(prefix, suffix)            prefix ## suffix
#define _UNIQUE_LABEL(prefix, suffix)  CAT(prefix, suffix)
#define UNIQUE_LABEL(prefix)           _UNIQUE_LABEL(prefix, __LINE__)

#define MY_MACRO(typ, condition)  if (condition) { \
                                   struct typ m = start_stuff(); goto UNIQUE_LABEL(enter);} \
                                  if (condition)  while(1) if (1) {end_stuff(); break;} \
                                                           else UNIQUE_LABEL(enter):

Note that this has a small performance and footprint impact when compiler optimization is disabled. Also, a debugger will seem jump back to the MY_MACRO line when running calling the end_stuff() function, which is not really desirable.

Also, you might want to use the macro inside a new block scope to avoid pollution your scope with the 'm' variable:

{MY_MACRO(Foo, condition) {
    m.foo = bar(1,2);
    m.baz = 17;
}}

Of course, using 'break' not inside a nested loop in the compound statement would skip the 'end_stuff()'. To allow for those to break the surrounding loop and still call 'end_stuff()', I think you'd have to enclose the compound statement with a start token and an end token as in:

#define  MY_MACRO_START(typ, condition)  if (condition) { \
                                          struct typ m = start_stuff(); do {

#define  MY_MACRO_EXIT                   goto UNIQUE_LABEL(done);} while (0); \
                                         end_stuff(); break; \
                                         UNIQUE_LABEL(done): end_stuff();}

MY_MACRO_START(foo, condition) {
    m.foo = bar(1,2);
    m.baz = 17;
} MY_MACRO_END

Note that because of the 'break' in that approach, the MY_MACRO_EXIT macro would only be usable inside a loop or switch. You could use a simpler implementation when not inside a loop:

#define  MY_MACRO_EXIT_NOLOOP  } while (0); end_stuff();}

I used 'condition' as a macro argument, but you may also embed it directly in the macro if desired.

like image 21
Eric Julien Avatar answered Sep 19 '22 22:09

Eric Julien