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

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

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捕获,这对我的特定用例来说是可接受的权衡。

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

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

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

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

 //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’作为宏参数,但如果需要,您也可以将它直接嵌入到宏中。

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

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

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

  1. 它会起作用吗? 答案一般是肯定的,但有一些陷阱。 正如里奇所提到的 ,其中一个是无人看守的逗号。 基本上,请记住宏扩展是一个复制和粘贴操作,预处理器不理解它复制和粘贴的代码。

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

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

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

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