23

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.

ugoren
  • 16,023
  • 3
  • 35
  • 65
hugomg
  • 68,213
  • 24
  • 160
  • 246
  • 6
    The do-while-false is a standard idiom that enables you to break out of the macro if you should need to. My only reservation about macros is that it makes debugging difficult, especially in crash situations. If there is a long macro block of about 100 lines, and after running for 5 hours, the code crashes somewhere in there on one of the hundreds of calls to the macro, you will have a hard time tracking it down. – cup Jun 19 '13 at 04:45
  • For compiling errors, use clang. It identifies syntax errors inside huge macros. For run-time errors, I usually use `gcc -E prog.c|grep -v ^#|indent > prog-dbg.c` and then compile and link `prog-dbg.c` to other code. It is clumsy but working. – user172818 Jun 19 '13 at 04:51
  • 1
    Your concept seems to be okay as it is ... but why don't you do a call back functions constructs to make your life easier? Is the inline speed very important? as @cup points out this is a pain to debug. – Ahmed Masud Jun 19 '13 at 05:07
  • @AhmedMasud: I should have mentioned in my question that the boiler plate includes some things that can't be passed as function arguments (the type of the struct, the names for some other things) and that I also need to do some `##` black magic on top of it all. A callback function that receives the struct as a parameter might work for the example in the question but in my real code the only alternative would be going back to writing the boilerplate by hand. – hugomg Jun 19 '13 at 05:09
  • @missingno aaah ... well then you need to make your magic a bit more magical I will post an alternate approach are you able to predict the compiler ? is it GCC? – Ahmed Masud Jun 19 '13 at 05:19
  • @missingno after reading up on nesC docs i think you are probably using the most efficient approach available without screwing around with other things. If you could split it up into function pointers you may be able to create a cleaner set of macros. – Ahmed Masud Jun 19 '13 at 05:29

7 Answers7

23

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.)

rici
  • 234,347
  • 28
  • 237
  • 341
  • It really helped me find out why I got the error: `error: macro "some_macro_name" passed 8 arguments, but takes just 7` – xhg Dec 11 '15 at 16:52
  • 2
    Apparently whatever you call these: `<>` (angle brackets?) don't either. Found this out trying to use code with more than one template argument – Sean Jan 24 '16 at 02:15
  • @sean: Yes, only parentheses means only parentheses. – rici Jan 24 '16 at 02:27
  • 1
    Who would have thought? – Sean Jan 24 '16 at 19:21
7

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.

Eric Julien
  • 141
  • 1
  • 2
1

You can put code block into a macro but you must be warned that it makes debugging a lot harder using a debugger. IMHO is better just to either write a function or cut'n'paste the lines of code.

Ed Heal
  • 59,252
  • 17
  • 87
  • 127
0

How about function pointers instead (and optionally inline functions)?

void do_stuff_inner_alpha(struct Foo *m)
{
    m->foo = bar(1,2); m->baz = 17;
}

void do_stuff_inner_beta(struct Foo *m)
{
    m->foo = bar(9, 13); m->baz = 445;
}


typedef void(*specific_modifier_t)(struct Foo *);

void do_stuff(specific_modifier_t func)
{
    if (condition){
        struct Foo m = start_stuff();
        func(&m); //this part varies
        end_stuff();
    }
}

int main(int argc, const char *argv[])
{
    do_stuff(do_stuff_inner_beta);

    return EXIT_SUCCESS;
}
Brian Cain
  • 14,403
  • 3
  • 50
  • 88
  • Unfortunately, in my rel code the types of the structs vary and are an extra argument to the macro. While this can be worked around with void pointers, there are also othre things that I can only do with macros due to technical reasons. – hugomg Jun 19 '13 at 05:11
  • Consider instead more signficant design changes in order to accommodate what might be a necessary abstraction layer. – Brian Cain Jun 19 '13 at 05:13
  • Unfortunatelly, I'm actually working with [nesC](https://en.wikipedia.org/wiki/NesC), a C dialect and some of the things I need to parametrize over are interface and type names names that don't lend themselves well to normal function abstraction. – hugomg Jun 19 '13 at 05:20
0

"Is it OK?" may mean two things:

  1. Will it work? Here the answer is generally yes, but there are pitfalls. One, as rici mentioned, is an unguarded comma. Basically, remember that macro expansion is a copy&paste operation, and the preprocessor doesn't understand the code it copies and pastes.

  2. Is it a good idea? I'd say the answer is generally no. It makes your code unreadable and hard to maintain. In some rare cases, this may be better than alternatives, if implemented well, but that's the exception.

Community
  • 1
  • 1
ugoren
  • 16,023
  • 3
  • 35
  • 65
0

Note that in C++ you could use a lambda the following way:

#include <iostream>

#define MY_MACRO(body) \
setup();\
body();\
teardown();\

int main() {
  int a = 1;
  MY_MACRO(([&]() mutable {
    std::cout << "Look, no setup" << std::endl;
    a++;
  }));
  std::cout << "a is now " << a << std::endl;
}

If you do this, you should first consider if there should instead be a function that plainly takes the lambda:

void withSetup(std::function<void ()> callback) {
  setup();
  callback();
  teardown();
}

int main() {
  withSetup([&]() {
    doStuff();
  });
}
Zain
  • 51
  • 6
  • While this question asks for c advice, I assume many/most people coming across this were writing c++ anyways. This question is the first that pops up when googling for c++. – Zain Jul 23 '21 at 12:43
-2

Before answering your question "is it OK to use macro" I'd like to know why you want to convert that block of code to macro. What's that you're trying to gain and at what cost?

If same block of code you're using repeatedly, it's better to convert that in a function, maybe an inline function and leave it to compiler to make it inline or not.

Should you run into crash\issue, debugging a macro is a tedious task.

calvinscorner
  • 121
  • 1
  • 7