可以使用代码块作为C宏的参数吗?

时间:2013-06-19 04:28:55

标签: c macros nesc

我的模式基本上是一些样板代码,其中一部分在中间变化

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

是否可以使宏将该中间代码块作为参数? C中的宏扩展规则看起来非常复杂,所以我不确定将来是否会出现任何可能会让我感到困扰的极端情况(特别是,我不明白如果我的代码如何分离宏参数其中有逗号)。

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

到目前为止,我唯一想到的是breakcontinue如果我在宏中使用循环语句而被捕获,这对我的特定用例来说是可接受的权衡。< / p>

编辑当然,如果可以,我会使用一些功能。我在这个问题中使用的示例是简化的,并没有展示只能用于宏魔术的位。

7 个答案:

答案 0 :(得分:15)

如果代码块没有无防护的逗号,您可以将代码块放入宏参数中。在您的示例中,参数中唯一的逗号是有问题的,因为它被括号括起来。

请注意括号保护逗号。括号([])和大括号({})不会。

答案 1 :(得分:4)

作为替代方案,您可以考虑使用复合语句之前的宏,如下所示。其中一个优点是所有调试器仍然可以进入复合语句,而复合语句作为宏参数方法则不然。

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

使用一些goto magic(是的,'goto'在某些情况下可能是邪恶的,但我们在C中有很少的选择),宏可以实现为:

#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):

请注意,禁用编译器优化时,这会对性能和占用空间产生很小的影响。此外,在运行调用end_stuff()函数时,调试器似乎会跳回到MY_MACRO行,这是不可取的。

此外,您可能希望在新的块范围内使用宏来避免使用'm'变量污染您的范围:

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

当然,在复合语句中使用'break'不在嵌套循环内会跳过'end_stuff()'。为了允许那些打破周围循环并仍然调用'end_stuff()',我认为你必须用一个开始标记和一个结束标记将复合语句括起来,如下所示:

#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

请注意,由于该方法中的“break”,MY_MACRO_EXIT宏只能在循环或开关中使用。不在循环内部时可以使用更简单的实现:

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

我使用'condition'作为宏参数,但如果需要,你也可以将它直接嵌入到宏中。

答案 2 :(得分:1)

您可以将代码块放入宏中,但必须警告您使用调试器会使调试变得更加困难。恕我直言最好只是编写一个函数或切断代码行。

答案 3 :(得分:0)

如何改变函数指针(以及可选的inline函数)?

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

答案 4 :(得分:0)

“可以吗?”可能意味着两件事:

  1. 会起作用吗?答案通常是肯定的,但存在陷阱。一,as rici mentioned,是一个无人看守的逗号。基本上,请记住宏扩展是一个复制和粘贴操作,预处理器不理解它复制和粘贴的代码。

  2. 这是个好主意吗?我会说答案通常是否定的。它使您的代码难以理解且难以维护。在极少数情况下,如果实施得当,这可能比替代方案更好,但这是例外。

答案 5 :(得分:0)

请注意,在 C++ 中,您可以通过以下方式使用 lambda:

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

如果你这样做,你应该首先考虑是否应该有一个直接接受 lambda 的函数:

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

int main() {
  withSetup([&]() {
    doStuff();
  });
}

答案 6 :(得分:-2)

在回答你的问题之前“使用宏是否可行”我想知道为什么要将这段代码转换为宏。你想要获得的是什么?成本是多少?

如果您反复使用相同的代码块,最好将其转换为函数,也可以是内联函数,并将其保留给编译器以使其内联或不内联。

如果遇到崩溃问题,调试宏是一项繁琐的工作。