20

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

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

到目前为止,我唯一能想到的就是如果我在宏中使用循环语句break并被continue捕获,这对于我的特定用例来说是一个可以接受的折衷方案。

编辑:当然,如果可以的话,我会使用函数。我在这个问题中使用的示例是简化的,并没有展示只能与宏魔法一起使用的位。

4

7 回答 7

20

您可以将代码块放入宏参数中,前提是它没有未保护的逗号。在您的示例中,参数中唯一的逗号受到保护,因为它被括号包围。

请注意,只有括号保护逗号。方括号 ( []) 和大括号 ( {}) 没有。<>(评论中提到的尖括号 ( ) 也没有。)

但是,如果代码块参数是宏的最后一个参数,您可以使用可变参数宏来增加灵活性。但请注意:增加的灵活性也意味着错误可能会被忽视。如果你这样做,你只需要确保括号是平衡的。(同样,只有括号对宏处理器很重要。)

于 2013-06-19T04:58:42.273 回答
7

作为替代方案,您可以考虑在复合语句之前使用宏,如下所示。这样做的优点之一是所有调试器仍然能够进入您的复合语句,而复合语句作为宏参数方法并非如此。

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

使用一些 goto 魔法(是的,'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

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

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

我使用“条件”作为宏参数,但如果需要,您也可以将其直接嵌入宏中。

于 2014-03-10T11:58:24.657 回答
1

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

于 2013-06-19T05:07:54.970 回答
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;
}
于 2013-06-19T05:08:41.783 回答
0

“没事吧?” 可能意味着两件事:

  1. 它会起作用吗?这里的答案通常是肯定的,但也有陷阱。一个,正如 rici 所提到的,是一个没有保护的逗号。基本上,请记住宏扩展是一个复制和粘贴操作,预处理器不理解它复制和粘贴的代码。

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

于 2013-06-19T05:51:36.650 回答
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();
  });
}
于 2021-07-23T12:38:37.270 回答
-2

在回答您的问题“是否可以使用宏”之前,我想知道您为什么要将该代码块转换为宏。你想要得到什么,付出什么代价?

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

如果您遇到崩溃\问题,调试宏是一项乏味的任务。

于 2013-06-19T05:10:47.470 回答