使用#ifdef和#define将函数调用变为可选的注释

16

是否可以做类似这样的事情

#ifdef SOMETHING
#define foo //
#else
#define foo MyFunction
#endif

这个想法是,如果定义了SOMETHING,则对foo(...)的调用变成注释(或者其他不会被评估或编译的内容),否则它将成为对MyFunction的调用。

我见过__noop被用过,但我不认为我能用它。

编辑:

我不认为我能在这里使用宏,因为MyFunction接受可变数量的参数。

此外,我希望使参数不被评估!(所以做一些像注释掉MyFunction主体的事情并不能真正给我我所需要的结果,因为参数仍然会被评估)


请检查您的编译器是否支持“可变参数宏”。 - ChrisW
你为什么想要这样做呢?也许有其他方法可以得到你需要的东西。因为没有上下文,你想要做的事情似乎相当危险... - gimpf
对于不支持可变参数宏的编译器来说,能够剥离调试字符串和函数调用是非常好的。这可以节省大量可执行空间。 - MSN
@gimpf 是的,它是用于在发布版本中删除跟踪/调试类型语句的。 - Daniel LeCheminant
11个回答

26

试试这个:

#ifdef SOMETHING
#define foo(x)
#else
#define foo(x) MyFunction(x)
#endif
如果您的函数有多个参数,则:
#ifdef SOMETHING
#define foo(x,y,z)
#else
#define foo(x,y,z) MyFunction(x,y,z)
#endif

如果你的函数有可变数量的参数,那么你的编译器可能支持所谓的"可变参数宏(variadic macros)",就像这样:

#ifdef SOMETHING
#define foo(...)
#else
#define foo(...) MyFunction(__VA_ARGS__)
#endif

我在实践中见过这种技巧被用于从发布版本中删除日志功能。但是,也请参考是否需要分别制作“调试”和“发布”版本?,其中有人质疑您是否应该拥有不同的版本。


或者,与其将函数调用重新定义为无操作,Jonathan对此答案的评论建议执行以下操作:

#ifdef SOMETHING
#define foo(...) do { if (false) MyFunction(__VA_ARGS__) } while (0)
#else
#define foo(...) do { if (true) MyFunction(__VA_ARGS__) } while (0)
#endif

这样做的原因是为了让函数调用始终被编译(因此不会出现对已删除变量的引用等不必要的错误),但只在需要时才调用:参见Kernighan&Pike的《程序设计实践》戈达德航天中心的编程标准。

以下内容来源于一个debug.h文件(源自1990年,因此不使用__VA_ARGS__):

/*
** Usage:  TRACE((level, fmt, ...))
** "level" is the debugging level which must be operational for the output
** to appear. "fmt" is a printf format string. "..." is whatever extra
** arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
** -- See chapter 8 of 'The Practice of Programming', by Kernighan and Pike.
*/
#ifdef DEBUG
#define TRACE(x)    db_print x
#else
#define TRACE(x)    do { if (0) db_print x; } while (0)
#endif /* DEBUG */

使用C99以后,就不再需要双括号技巧了。除非需要兼容C89,否则新代码不应该使用它。


通常情况下,您应该将非函数调用定义为 ((void)0),以避免编译问题。话虽如此,在除表达式之外的情况下,我没有想到它真正有影响的场景。而在表达式中,您可能不会使用 void 强制转换。 - Jonathan Leffler
我看到的一个问题是,对于每个您想要有条件执行的函数,都必须定义一个新的宏。将整个函数调用封装在宏中将适用于任意数量的函数,并且更加明确,即不太可能引起混淆(“我的函数调用去哪了?”)。 - Christoph
添加了一个段落来说明这是用于什么,并引用一个问题,探讨这是否总是一个好主意。 - ChrisW
1
啊!不要轻率地使用“if (ConditionalLoggingEnabled) { MyFunction(...); }”而忽略了“else”。例如,“if (somevar) TRACE(somevar); else TRACE(somevar+1);”一旦“TRACE”扩展为额外的if-clause,它就会因else的歧义而变得非常混乱。 - Phil Hord
1
@phord:是的,你说得对。do { if (conditional) MyFunction(...); } while (0) -- @ChrisW:不,花括号不够,因为分号会让人困惑。 - Jonathan Leffler
显示剩余12条评论

5
也许更简单的方法是有条件地省略函数体?
void MyFunction() {
#ifndef SOMETHING
    <body of function>
#endif
}

除非你明确不想执行某个函数调用,否则这似乎是实现你目标的一种清晰方式。


真的。我认为如果您不想评估参数,那么您可能必须走得更远,例如#ifdefing出所有调用。在大多数编译器上,“#define foo(x)”解决方案也将评估您的参数。 - MattK
不仅会评估参数,而且它们的数据也会被包含在内。如果函数是一个记录函数并且参数是揭示应用程序功能的巨大字符串,则这种情况并不好。 - sharptooth

3

很遗憾,当前的C++版本不支持可变宏。

但是,你可以这样做:

#ifdef SOMETHING
#define foo
#else
#define foo(args) MyFunction args
#endif

// you call it with double parens:
foo((a, b, c));

我使用调试日志函数来实现这一点,以便(a) 它们在生产构建中完全消失,(b) 我可以传递一个格式字符串和任意数量的参数。 - Graeme Perrow

2
如果您不想调用foo函数,可以将其定义为:

```

void foo() {}

任何对foo()的调用都应该采用优化方式。

我认为你应该将其声明为内联,以确保它被优化掉。它还应该支持可变参数,所以我认为这可能是最好的解决方案。 - rmeador
这取决于编译器。我看过VC++优化掉带有代码但没有副作用的函数。当您尝试进行基准测试时,这非常令人沮丧;-) - Ferruccio

2
以下是类似的内容:

这个问题大概是这样的:

#ifdef NDEBUG
#define DEBUG(STATEMENT) ((void)0)
#else
#define DEBUG(STATEMENT) (STATEMENT)
#endif

您可以像这样使用它来记录调试信息:

DEBUG(puts("compile with -DNDEBUG and I'm gone"));

使用 C99 可变宏和 __func__ 标识符来生成带有额外调试信息的格式化输出的非通用版本可能如下所示:
#ifdef NDEBUG
#define Dprintf(FORMAT, ...) ((void)0)
#define Dputs(MSG) ((void)0)
#else
#define Dprintf(FORMAT, ...) \
    fprintf(stderr, "%s() in %s, line %i: " FORMAT "\n", \
        __func__, __FILE__, __LINE__, __VA_ARGS__)
#define Dputs(MSG) Dprintf("%s", MSG)
#endif

这里是如何使用这些宏的示例:

以下是使用步骤:

Dprintf("count = %i", count);
Dputs("checkpoint passed");

2

很可能,你不想像建议的那样简单地“删除代码”,因为调用者期望参数的副作用会发生。以下是一些有问题的调用者片段,应该让你思考:

// pre/post increment inside method call:
MyFunction(i++); 

// Function call (with side effects) used as method argument: 
MyFunction( StoreNewUsernameIntoDatabase(username) ); 

如果您想简单地禁用MyFunction,只需执行以下操作:
#define MyFunction(x) 

如果调用方所期望的副作用消失了,那么他们的代码将出现问题,并且很难调试。我喜欢上面提到的 "sizeof" 建议,也喜欢通过 #ifdef 禁用 MyFunction() 的建议,尽管这意味着所有调用者都会得到相同版本的 MyFunction()。从您的问题陈述中,我推断这实际上不是您想要的。
如果您真的需要通过预处理器定义在每个源文件上禁用 MyFunction(),那么我会这样做:
#ifdef SOMETHING 
#define MyFunction(x) NoOp_MyFunction(x) 

int NoOp_MyFunction(x) { } 
#endif 

你甚至可以将NoOp_MyFunction()的实现包含在MyFunction()的源代码和头文件中。你还可以在NoOp_MyFunction()中添加额外的日志或调试信息。

sizeof 相对于副作用也存在同样的问题。 - Logan Capaldo

1
不可以,C和C++标准规定你不能将 #define 定义为注释。
#define foo //

无法工作。


1
#ifdef SOMETHING
#define foo sizeof
#else
#define foo MyFunction
#endif

我假设foo是一个printf风格的函数?无论如何,这不适用于零参数函数,但如果是这种情况,你已经知道该怎么做了。如果你真的想要严谨一些,可以使用(void)sizeof,但那可能是不必要的。


1
我有点不太愿意发布这个答案,因为它使用的宏技巧可能会成为问题的根源。然而 - 如果你想要消除的函数调用总是单独在语句中使用(即,它们从未是更大表达式的一部分),那么类似以下的代码可能有效(并且它可以处理可变参数):
#ifdef SOMETHING
#define foo (1) ? ((void) 0) : (void)
#else
#define foo MyFunction
#endif

所以,如果你有这行代码:

foo( "this is a %s - a++ is %d\n", "test", a++);

在预处理步骤之后,它将最终变成以下之一:

MyFunction( "this is a %s - a++ is %d\n", "test", a++);

或者

(1) ? ((void) 0) : (void)( "this is a %s - a++ is %d\n", "test", a++);

这将伪函数的参数列表转换为一堆由逗号运算符分隔的表达式,这些表达式永远不会被评估,因为条件始终返回((void) 0)结果。

这个变体与ChriSW和Jonathan Leffler建议的东西非常接近:

#ifdef SOMETHING
#define foo if (0) MyFunction
#else
#define foo if (1) MyFunction
#endif

这与要求编译器支持可变参宏(__VA_ARGS__)的方法略有不同。

我认为这可以用于消除调试跟踪函数调用,这些函数调用通常从未组合成更大的表达式,但除此之外,我认为这是一种危险的技术。

请注意潜在的问题 - 特别是如果调用中的参数产生副作用(这是宏的常见问题 - 不仅仅是这个hack)。在这个示例中,只有在构建中定义了SOMETHING时,a++才会被计算。因此,如果调用后的代码依赖于增加的a的值,则其中一个构建存在错误。


0

在每次调用myFunction时,加上什么呢?

#ifdef SOMETHING
myFunction(...);
#endif

?


1
第二次调用后,这种方法变得非常笨拙。不要走这条路。 - Jonathan Leffler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接