你能在C语言中使用#define定义注释吗?

35

我正在尝试做一个调试系统,但它似乎无法正常工作。

我的目标是实现以下功能:

#ifndef DEBUG
    #define printd //
#else
    #define printd printf
#endif

有没有一种方法可以实现这个?我有许多调试消息,我不想这样做:

if (DEBUG)
    printf(...)

code

if (DEBUG)
    printf(...)

...

1
直接复制自https://dev59.com/MXI-5IYBdhLWcg3w18Z3#1644898,可能还有其他副本。 - Jonathan Leffler
4
@JonathanLeffler 这个“使用案例”(创建一个仅用于调试的打印宏)是重复的,可以参考https://dev59.com/MXI-5IYBdhLWcg3w18Z3#1644898,但字面上问的问题(我能在`#define`中包含`//`吗?)不是-而且字面上问的问题对其他人来说是有趣和有用的。这种重叠很不幸,但这里有一个明显有价值的独立问题;它不应该被关闭。 - Mark Amery
@MarkAmery:这个标题是一个 'XY 问题' —— 用户想要实现的解决方案在被提名的重复问题中,但是这个问题正在寻求一些无法实现他们想要的东西。 - Jonathan Leffler
2
@JonathanLeffler 不管您认为标题是否有问题,如果我没有在谷歌搜索结果中点击这个标题,我就不会找到这些答案或想到以某种方式进行操作。对我来说,问题更多地是如何在不需要到处使用 #ifdefs 的情况下从生产代码中剥离代码,由于 OP 的次要问题,我找到了一些设想,使我走上了正确的轨道。我不会搜索“调试打印”的问题 - 那根本不是我的用例! - Username Obfuscation
12个回答

36

不能。在开始任何预处理指令的处理之前,注释将从代码中删除。因此,您无法将注释包含在宏中。

此外,通过使用任何宏技巧“形成”注释的任何尝试都不能保证有效。编译器不需要将“延迟”的注释视为注释。

实现您想要的最佳方法是在C99中使用具有可变参数的宏(或者可能使用编译器扩展)。


25

一个常见的技巧是这样做:

#ifdef DEBUG
  #define OUTPUT(x) printf x
#else
  #define OUTPUT(x)
#endif

#include <stdio.h>
int main(void)
{   
  OUTPUT(("%s line %i\n", __FILE__, __LINE__));

  return 0;
}

这种方式使您可以完全使用printf()的功能,但您必须忍受双括号以使宏起作用。

双括号的目的是:需要一个括号集来表示它是宏调用,但在C89中,宏中不能有不确定数量的参数。但是,通过将参数放在自己的一组括号中,它们会被解释为单个参数。当定义了DEBUG时,宏展开时,替换文本是单词printf后跟单个参数,该参数实际上是几个带括号的项。然后,这些括号被解释为printf函数调用中需要的括号,因此它可以正常工作。


1
另外,您可以使用OUTPUT(x...) printf(x)来避免双重括号。 - Brian Postow
1
@Brian 如果你需要可变数量的参数,这种方法是行不通的。请参见详细说明。 - Tim

20

使用C99方式:

#ifdef DEBUG
    #define printd(...) printf(__VA_ARGS__)
#else
    #define printd(...)
#endif

好的,这个不需要C99但是假设编译器已经针对发布版本进行了优化:

#ifdef DEBUG
    #define printd printf
#else
    #define printd if (1) {} else printf
#endif

正确,但不太可移植。 - Tim Post
好多了,但是“便携”版本仍然没有禁用参数的有效性检查。在非C99实现中正确的方法在Tim的答案中给出。 - AnT stands with Russia
这是我认为它的好处。为什么要在调试版本中禁用代码的有效性检查呢? - Xeor
简单来说,因为那段代码在发布版本中可能是无效的。调试代码在发布版本中无效并没有什么问题。而且,大多数情况下都是这样的。 - AnT stands with Russia
2
@AndreyT:尽管这个问题相当古老且已经得到了回答,但我对您的评论很感兴趣。您能否提供或指出一个示例,在发布版本中调试代码可能无效的情况?非常感谢。 - Carla Álvarez
@CarlaÁlvarez,我曾多次见过这种情况,即调试代码在发布版本中无效,原因是调试跟踪变量在发布代码中根本不存在。你不能有一个printf引用调试变量,因为这些变量在发布代码中不存在,而且你也不希望额外的变量占用发布代码的空间。 - piCookie

10

在某些编译器上(包括MS VS2010),这将起作用,

#define CMT / ## /

但并不保证所有编译器都能正常运行。


8
您可以将所有的调试调用放在一个函数中,让我们称之为printf_debug,并将DEBUG放置在此函数内部。编译器会优化空函数。

谢谢,终于恢复理智了...除了大部分工作是预处理器完成的。 - Tim Post
在这种情况下,您可以直接调用您的函数printd。 - tadman
2
这并不会禁用参数的评估,也不会禁用参数必须有效(例如已声明)的要求。换句话说,它没有做到所要求的。 - AnT stands with Russia
@AndreyT,如果进行了任何类型的链接时优化,编译器将内联空函数并删除用于评估参数的死代码。 - Jay Conrod
2
问题在于,如果参数在发布环境下无效,它根本无法编译。它不会进入“优化”阶段。 - AnT stands with Russia

2
标准方式是使用 </p>
#ifndef DEBUG
    #define printd(fmt, ...)  do { } while(0)
#else
    #define printd(fmt, ...) printf(fmt, __VA_ARGS__)
#endif

这样,当你在末尾添加一个分号时,它会按照你的意愿执行。由于没有操作,编译器将编译掉“do...while”。


1
你可以利用if。例如,
#ifdef debug
    #define printd printf
#else 
    #define printd if (false) printf
#endif

如果您设置了像-O2这样的优化标志,编译器将删除这些无法访问的代码。这种方法对于std::cout也很有用。

1

未经测试: 编辑:已经测试,现在我自己在使用它 :)

#define DEBUG 1
#define printd(fmt,...) if(DEBUG)printf(fmt, __VA_ARGS__)

需要你不仅定义DEBUG,还要给它一个非零值。

附录: 也可以与std::cout很好地配合使用。


1
在C++17中,我喜欢使用constexpr来处理类似这样的内容。
#ifndef NDEBUG
constexpr bool DEBUG = true;
#else
constexpr bool DEBUG = false;
#endif

然后你可以这样做。
if constexpr (DEBUG) /* debug code */

需要注意的是,与预处理器宏不同,您的作用范围受到限制。您不能在一个调试条件中声明变量并从另一个条件中访问它们,也不能在函数外部作用域中使用它们。


3
这不是一个答案,问题标记为C语言,而答案是针对C++的。这两种语言是不同的。 - SergeyA

0
正如McKay所指出的那样,如果你只是试图用//替换printd,你会遇到问题。相反,你可以使用可变参数宏来将printd替换为一个什么也不做的函数,如下所示。
#ifndef DEBUG
    #define printd(...) do_nothing()
#else
    #define printd(...) printf(__VA_ARGS__)
#endif

void do_nothing() { ; }

使用像GDB这样的调试器也可能有所帮助,但有时候一个快速的printf就足够了。


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