首页 文章

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

提问于
浏览
15

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

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

是否可以使宏taht将该中间代码块作为参数? 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 如果我在我的宏中使用循环语句而被捕获,这对我的特定用例来说是一个可接受的权衡 .

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

6 回答

  • 0

    您可以将代码块放入宏参数中,前提是它没有无防护的逗号 . 在您的示例中,参数中唯一的逗号被保护,因为它被括号括起来 .

    请注意,只有括号保护逗号 . 括号( [] )和大括号( {} )没有 .

  • 15

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

    //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
    

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

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

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

  • 4

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

  • -2

    如何改变函数指针(以及可选的 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;
    }
    
  • 0

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

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

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

  • 1

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

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

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

相关问题