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

核能气质少年 提交于 2019-12-04 15:58:51

问题


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.


回答1:


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.




回答2:


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.




回答3:


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.




回答4:


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;
}



回答5:


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




回答6:


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.



来源:https://stackoverflow.com/questions/17182877/is-it-ok-to-use-a-code-block-as-an-argument-for-a-c-macro

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!