【问题标题】:Is it OK to use a code block as an argument for a C macro?可以使用代码块作为 C 宏的参数吗?
【发布时间】:2013-06-15 11:51:21
【问题描述】:

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

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 会被捕获,这对于我的特定用例来说是一个可以接受的折衷方案。

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

【问题讨论】:

  • do-while-false 是一个标准的习惯用法,如果需要,您可以跳出宏。我对宏的唯一保留是它使调试变得困难,尤其是在崩溃情况下。如果有一个大约 100 行的长宏块,并且在运行 5 小时后,代码在对宏的数百次调用中的某处崩溃,您将很难跟踪它。
  • 对于编译错误,使用clang。它识别巨大宏中的语法错误。对于运行时错误,我通常使用gcc -E prog.c|grep -v ^#|indent > prog-dbg.c,然后编译并将prog-dbg.c 链接到其他代码。它很笨拙但很有效。
  • 您的概念似乎还可以……但是您为什么不做一个回调函数构造以使您的生活更轻松?内联速度很重要吗?正如@cup 指出的那样,调试起来很痛苦。
  • @AhmedMasud:我应该在我的问题中提到样板包含一些不能作为函数参数传递的东西(结构的类型,其他一些东西的名称)并且我还需要在这一切之上做一些## 黑魔法。将结构作为参数接收的回调函数可能适用于问题中的示例,但在我的真实代码中,唯一的选择是回到手动编写样板。
  • @missingno aaah ...那么你需要让你的魔法更神奇我会发布一个替代方法你能预测编译器吗?是海合会吗?

标签: c macros nesc


【解决方案1】:

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

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

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

【讨论】:

  • 它确实帮助我找出了错误的原因:error: macro "some_macro_name" passed 8 arguments, but takes just 7
  • 显然不管你怎么称呼这些:<>(尖括号?)也不是。在尝试使用具有多个模板参数的代码时发现了这一点
  • @sean:是的,只有括号意味着只有括号。
  • 谁能想到?
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      函数指针(以及可选的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;
      }
      

      【讨论】:

      • 不幸的是,在我的 rel 代码中,结构的类型各不相同,并且是宏的额外参数。虽然这可以用 void 指针来解决,但由于技术原因,我也只能用宏来做其他事情。
      • 请考虑更重要的设计更改,以适应可能是必要的抽象层。
      • 不幸的是,我实际上正在使用 nesC,一种 C 方言,我需要参数化的一些东西是接口和类型名称,它们不适合正常的函数抽象.
      【解决方案5】:

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

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

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

      【讨论】:

        【解决方案6】:

        请注意,在 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();
          });
        }
        

        【讨论】:

        • 虽然这个问题需要 c 建议,但我认为很多/大多数遇到这个问题的人无论如何都在编写 c++。这个问题是谷歌搜索 c++ 时弹出的第一个问题。
        【解决方案7】:

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-09-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-11-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多