我的模式基本上是一些样板代码,其中一部分在中间变化
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
如果我在宏中使用循环语句而被捕获,这对我的特定用例来说是可接受的权衡。< / p>
编辑当然,如果可以,我会使用一些功能。我在这个问题中使用的示例是简化的,并没有展示只能用于宏魔术的位。
答案 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)
“可以吗?”可能意味着两件事:
会起作用吗?答案通常是肯定的,但存在陷阱。一,as rici mentioned,是一个无人看守的逗号。基本上,请记住宏扩展是一个复制和粘贴操作,预处理器不理解它复制和粘贴的代码。
这是个好主意吗?我会说答案通常是否定的。它使您的代码难以理解且难以维护。在极少数情况下,如果实施得当,这可能比替代方案更好,但这是例外。
答案 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)
在回答你的问题之前“使用宏是否可行”我想知道为什么要将这段代码转换为宏。你想要获得的是什么?成本是多少?
如果您反复使用相同的代码块,最好将其转换为函数,也可以是内联函数,并将其保留给编译器以使其内联或不内联。
如果遇到崩溃问题,调试宏是一项繁琐的工作。