尝试创建一个宏,可以在定义DEBUG时用于打印调试消息,如下面的伪代码:
#define DEBUG 1 #define debug_print(args ...) if (DEBUG) fprintf(stderr, args)
如何用宏实现这一目标?
#define debug_print(fmt, ...) \ do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
它假定您使用的是C99(早期版本不支持变量参数列表表示法).这个do { ... } while (0)
习惯用法确保代码就像一个语句(函数调用).无条件使用代码可确保编译器始终检查您的调试代码是否有效 - 但优化器将在DEBUG为0时删除代码.
如果您想使用#ifdef DEBUG,请更改测试条件:
#ifdef DEBUG #define DEBUG_TEST 1 #else #define DEBUG_TEST 0 #endif
然后使用DEBUG_TEST我使用DEBUG.
如果你坚持一个字符串格式字符串(可能反正是个好主意),你也可以介绍之类的东西__FILE__
,__LINE__
并且__func__
到输出,从而可以提高诊断:
#define debug_print(fmt, ...) \ do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ __LINE__, __func__, __VA_ARGS__); } while (0)
这依赖于字符串连接来创建比程序员编写的更大的格式字符串.
如果您坚持使用C89并且没有有用的编译器扩展,那么就没有一种特别干净的方法来处理它.我以前使用的技术是:
#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)
然后,在代码中,写:
TRACE(("message %d\n", var));
双括号是至关重要的 - 这就是为什么你在宏观扩张中有一个有趣的符号.和以前一样,编译器总是检查代码的语法有效性(这很好),但是如果DEBUG宏的计算结果为非零,优化器只会调用打印函数.
这确实需要一个支持函数 - 示例中的dbg_printf() - 来处理'stderr'之类的东西.它要求你知道如何编写varargs函数,但这并不难:
#include#include void dbg_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); }
当然,您也可以在C99中使用此技术,但该__VA_ARGS__
技术更简洁,因为它使用常规函数表示法,而不是双括号黑客.
[ 重新发表对另一个答案的评论.]
上面的C99和C89实现背后的一个中心思想是编译器本身总是看到调试类似printf的语句.这对于长期代码很重要 - 代码将持续十年或两年.
假设一段代码多年来一直处于休眠状态(稳定),但现在需要进行更改.您重新启用调试跟踪 - 但是必须调试调试(跟踪)代码是令人沮丧的,因为它引用了在稳定维护期间已重命名或重新键入的变量.如果编译器(后预处理器)始终看到print语句,则它确保任何周围的更改都没有使诊断无效.如果编译器没有看到print语句,它就无法保护您免受自己的粗心(或同事或合作者的疏忽).参见Kernighan和Pike 的"编程实践 ",尤其是第8章(另见TPOP维基百科).
这是"在那里,完成了"经验 - 我基本上使用了其他答案中描述的技术,其中非调试版本多年(超过十年)没有看到类似printf的语句.但是我在TPOP中看到了建议(参见我之前的评论),然后在几年后启用了一些调试代码,并遇到了更改上下文的问题,打破了调试.有几次,打印总是经过验证,这使我免于后来的问题.
我使用NDEBUG仅控制断言,并使用单独的宏(通常为DEBUG)来控制是否在程序中内置调试跟踪.即使内置调试跟踪,我也经常不希望调试输出无条件地出现,所以我有机制来控制输出是否出现(调试级别,而不是fprintf()
直接调用,我调用只有条件打印的调试打印功能)所以代码的相同版本可以打印或不打印基于程序选项).我还有一个用于更大程序的代码的"多子系统"版本,因此我可以让程序的不同部分产生不同的跟踪量 - 在运行时控制下.
我主张对于所有构建,编译器应该看到诊断语句; 但是,除非启用了调试,否则编译器不会为调试跟踪语句生成任何代码.基本上,这意味着每次编译时编译器都会检查所有代码 - 无论是发布还是调试.这是件好事!
/* @(#)File: $RCSfile: debug.h,v $ @(#)Version: $Revision: 1.2 $ @(#)Last changed: $Date: 1990/05/01 12:55:39 $ @(#)Purpose: Definitions for the debugging system @(#)Author: J Leffler */ #ifndef DEBUG_H #define DEBUG_H /* -- Macro Definitions */ #ifdef DEBUG #define TRACE(x) db_print x #else #define TRACE(x) #endif /* DEBUG */ /* -- Declarations */ #ifdef DEBUG extern int debug; #endif #endif /* DEBUG_H */
/* @(#)File: $RCSfile: debug.h,v $ @(#)Version: $Revision: 3.6 $ @(#)Last changed: $Date: 2008/02/11 06:46:37 $ @(#)Purpose: Definitions for the debugging system @(#)Author: J Leffler @(#)Copyright: (C) JLSS 1990-93,1997-99,2003,2005,2008 @(#)Product: :PRODUCT: */ #ifndef DEBUG_H #define DEBUG_H #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ /* ** 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 */ #ifndef lint #ifdef DEBUG /* This string can't be made extern - multiple definition in general */ static const char jlss_id_debug_enabled[] = "@(#)*** DEBUG ***"; #endif /* DEBUG */ #ifdef MAIN_PROGRAM const char jlss_id_debug_h[] = "@(#)$Id: debug.h,v 3.6 2008/02/11 06:46:37 jleffler Exp $"; #endif /* MAIN_PROGRAM */ #endif /* lint */ #includeextern int db_getdebug(void); extern int db_newindent(void); extern int db_oldindent(void); extern int db_setdebug(int level); extern int db_setindent(int i); extern void db_print(int level, const char *fmt,...); extern void db_setfilename(const char *fn); extern void db_setfileptr(FILE *fp); extern FILE *db_getfileptr(void); /* Semi-private function */ extern const char *db_indent(void); /**************************************\ ** MULTIPLE DEBUGGING SUBSYSTEMS CODE ** \**************************************/ /* ** Usage: MDTRACE((subsys, level, fmt, ...)) ** "subsys" is the debugging system to which this statement belongs. ** The significance of the subsystems is determined by the programmer, ** except that the functions such as db_print refer to subsystem 0. ** "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. */ #ifdef DEBUG #define MDTRACE(x) db_mdprint x #else #define MDTRACE(x) do { if (0) db_mdprint x; } while (0) #endif /* DEBUG */ extern int db_mdgetdebug(int subsys); extern int db_mdparsearg(char *arg); extern int db_mdsetdebug(int subsys, int level); extern void db_mdprint(int subsys, int level, const char *fmt,...); extern void db_mdsubsysnames(char const * const *names); #endif /* DEBUG_H */
凯尔勃兰特问道:
无论如何要做到这一点,
debug_print
即使没有争论仍然有效吗?例如:debug_print("Foo");
有一个简单,老式的黑客:
debug_print("%s\n", "Foo");
下面显示的仅GCC解决方案也为此提供了支持.
但是,您可以使用直接C99系统来执行以下操作:
#define debug_print(...) \ do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)
与第一个版本相比,您丢失了需要'fmt'参数的有限检查,这意味着有人可能尝试在没有参数的情况下调用'debug_print()'(但参数列表中的尾随逗号fprintf()
将无法编译) .失去检查是否是一个问题是值得商榷的.
一些编译器可能会为宏中处理可变长度参数列表的其他方法提供扩展.具体来说,正如Hugo Ideler在评论中首次提到的那样,GCC允许您省略通常出现在宏的最后一个"固定"参数之后的逗号.它还允许您##__VA_ARGS__
在宏替换文本中使用,该文本删除符号前面的逗号,但前提是前一个标记是逗号:
#define debug_print(fmt, ...) \ do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)
此解决方案保留了在格式化后接受可选参数的同时需要format参数的好处.
Clang还支持GCC兼容性技术.
这里的目的是
do while
什么?
您希望能够使用宏,因此它看起来像一个函数调用,这意味着它将跟随一个分号.因此,您必须打包宏体以适应.如果您使用if
没有周围环境的声明do { ... } while (0)
,您将拥有:
/* BAD - BAD - BAD */ #define debug_print(...) \ if (DEBUG) fprintf(stderr, __VA_ARGS__)
现在,假设你写道:
if (x > y) debug_print("x (%d) > y (%d)\n", x, y); else do_something_useful(x, y);
不幸的是,该缩进并不反映流的实际控制,因为预处理器生成的代码与此相当(为了强调实际含义而添加了缩进和括号):
if (x > y) { if (DEBUG) fprintf(stderr, "x (%d) > y (%d)\n", x, y); else do_something_useful(x, y); }
宏的下一次尝试可能是:
/* BAD - BAD - BAD */ #define debug_print(...) \ if (DEBUG) { fprintf(stderr, __VA_ARGS__); }
现在相同的代码片段产生:
if (x > y) if (DEBUG) { fprintf(stderr, "x (%d) > y (%d)\n", x, y); } ; // Null statement from semi-colon after macro else do_something_useful(x, y);
而else
现在是一个语法错误.该do { ... } while(0)
循环避免了这两个问题.
还有另一种编写可能有效的宏的方法:
/* BAD - BAD - BAD */ #define debug_print(...) \ ((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))
这使程序片段显示为有效.该(void)
投防止它在需要的值的上下文中使用-但它可以作为一个逗号算哪里的左操作数do { ... } while (0)
的版本不能.如果您认为应该能够将调试代码嵌入到这些表达式中,您可能更喜欢这样.如果您希望将调试打印作为完整语句,那么do { ... } while (0)
版本更好.请注意,如果宏的主体涉及任何分号(粗略地说),那么您只能使用do { ... } while(0)
表示法.它总是有效; 表达式声明机制可能更难以应用.您可能还会使用您希望避免的表达式表单从编译器获得警告; 它取决于编译器和您使用的标志.
TPOP之前在http://plan9.bell-labs.com/cm/cs/tpop和http://cm.bell-labs.com/cm/cs/tpop,但现在都是(2015-08-10)破碎.
如果您很好奇,可以在我的SOQ(Stack Overflow Questions)存储库中的GitHub中查看此代码作为文件debug.c
,debug.h
并mddebug.c
在
src/libsoq
子目录中查看.
我使用这样的东西:
#ifdef DEBUG #define D if(1) #else #define D if(0) #endif
比我只使用D作为前缀:
D printf("x=%0.3f\n",x);
编译器看到调试代码,没有逗号问题,它可以在任何地方使用.当它还printf
不够时,它也可以工作,例如当您必须转储数组或计算一些对程序本身多余的诊断值时.
编辑:好的,它可能会产生一个问题,当有一个else
可以被注入拦截的地方if
.这是一个覆盖它的版本:
#ifdef DEBUG #define D #else #define D for(;0;) #endif
对于便携式(ISO C90)实现,您可以使用双括号,如下所示;
#include#include #ifndef NDEBUG # define debug_print(msg) stderr_printf msg #else # define debug_print(msg) (void)0 #endif void stderr_printf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } int main(int argc, char *argv[]) { debug_print(("argv[0] is %s, argc is %d\n", argv[0], argc)); return 0; }
或者(hackish,不推荐它)
#include#define _ , #ifndef NDEBUG # define debug_print(msg) fprintf(stderr, msg) #else # define debug_print(msg) (void)0 #endif int main(int argc, char *argv[]) { debug_print("argv[0] is %s, argc is %d"_ argv[0] _ argc); return 0; }
这是我使用的版本:
#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
我会做点什么的
#ifdef DEBUG #define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) #else #define debug_print(fmt, ...) do {} while (0) #endif
我认为这更清洁了.
根据http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html,应该有一个##
之前__VA_ARGS__
.
否则,宏#define dbg_print(format, ...) printf(format, __VA_ARGS__)
将无法编译以下示例:dbg_print("hello world");
.
#define debug_print(FMT, ARGS...) do { \ if (DEBUG) \ fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \ } while (0)