C #definemacros用于debugging打印

尝试创build一个可以在DEBUG定义时用于打印debugging消息的macros,如下面的伪代码:

#define DEBUG 1 #define debug_print(args ...) if (DEBUG) fprintf(stderr, args) 

这是如何完成一个macros?

如果您使用C99编译器

 #define debug_print(fmt, ...) \ do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0) 

它假定您正在使用C99(早期版本中不支持可变参数列表表示法)。 do { ... } while (0)成语确保代码的行为像一个语句(函数调用)。 代码的无条件使用确保编译器始终检查您的debugging代码是否有效 – 但是当DEBUG为0时,优化器将删除代码。

如果您想使用#ifdef DEBUG,请更改testing条件:

 #ifdef DEBUG #define DEBUG_TEST 1 #else #define DEBUG_TEST 0 #endif 

然后在DEBUG中使用DEBUG_TEST。

如果你坚持使用string格式string(可能是一个好主意),你也可以在输出中引入诸如__func__ __FILE____func____func__类的东西,这可以改善诊断:

 #define debug_print(fmt, ...) \ do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ __LINE__, __func__, __VA_ARGS__); } while (0) 

这依赖string连接来创build比程序员写的更大的格式string。

如果您使用C89编译器

如果你坚持使用C89而没有有用的编译器扩展,那么就没有一个特别干净的方法来处理它。 我曾经使用的技术是:

 #define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0) 

然后,在代码中写下:

 TRACE(("message %d\n", var)); 

双括号是至关重要的 – 这就是为什么你在macros观扩张中有一个有趣的符号。 和以前一样,编译器总是检查代码的语法有效性(这是很好的),但是如果DEBUGmacros的计算结果为非零,那么优化器只会调用打印函数。

这确实需要一个支持函数 – 例如dbg_printf()来处理像“stderr”这样的东西。 它需要你知道如何编写可变参数函数,但这并不难:

 #include <stdarg.h> #include <stdio.h> void dbg_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); } 

当然,您也可以在C99中使用这种技术,但是__VA_ARGS__技术更简洁,因为它使用常规function符号,而不是双括号。

为什么编译器总是看到debugging代码是至关重要的?

[ 重新评论对另一个答案的评论。 ]

在上面的C99和C89实现背后的一个中心思想是编译器本身总是看到类似于debuggingprintf的语句。 这对于长达十年或两年的长期代码是很重要的。

假设一段代码大部分时间处于hibernate状态(稳定),但现在需要改变。 您可以重新启用debugging跟踪 – 但是在debugging(跟踪)代码debugging过程中,因为它涉及到在稳定维护期间重命名或重新定义的variables,这是令人沮丧的。 如果编译器(后处理器)总是看到打印语句,则确保任何周围的更改都不会使诊断无效。 如果编译器没有看到打印语句,它不能保护你免受你自己的疏忽(或你的同事或合作者的疏忽)。 参见Kernighan和派克的“编程实践 ”,特别是第8章(另见维基百科TPOP )。

这是'那里,做了'的经验 – 我基本上使用其他答案中描述的技术,其中非debugging版本多年(超过十年)没有看到类似printf的语句。 但是我碰到了TPOP的build议(请参阅我以前的评论),然后在几年后启用了一些debugging代码,并遇到了更改上下文的问题,从而打破了debugging。 几次,总是validation了印刷,从而避免了以后的问题。

我使用NDEBUG仅控制断言,并使用单独的macros(通常是DEBUG)来控制是否在程序中内置debugging跟踪。 即使内置debugging跟踪,我经常不希望debugging输出无条件出现,所以我有机制来控制输出是否出现(debugging级别,而不是直接调用fprintf(),我调用一个debugging打印function只有有条件地打印,所以相同的代码构build可以打印或不打印基于程序选项)。 对于更大的程序,我也有一个“多个子系统”版本的代码,这样我可以在运行时控制下让程序的不同部分产生不同数量的跟踪。

我主张,对于所有的构build,编译器应该看到诊断语句; 但是,除非启用debugging,否则编译器不会为debugging跟踪语句生成任何代码。 基本上,这意味着每次编译时,编译器都会检查您的所有代码,无论是用于发布还是debugging。 这是一件好事!

debug.h – 版本1.2(1990-05-01)

 /* @(#)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 */ 

debug.h – 版本3.6(2008-02-11)

 /* @(#)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 */ #include <stdio.h> extern 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 */ 

单参数C99变体

凯尔·布​​兰特问道:

无论如何做到这一点,所以即使没有参数debug_print仍然工作? 例如:

  debug_print("Foo"); 

有一个简单的,老式的黑客:

 debug_print("%s\n", "Foo"); 

仅GCC解决scheme也为此提供支持。

但是,您可以使用以下方法直接使用C99系统:

 #define debug_print(...) \ do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0) 

与第一个版本相比,您失去了需要“fmt”参数的有限检查,这意味着有人可以不带任何参数地调用“debug_print()”。 检查的损失是否是一个问题是有争议的。

GCC特有的技术

有些编译器可能会提供其他方法来处理macros中的变长参数列表。 具体来说,正如雨果·伊德勒 ( Hugo Ideler)的评论中首先提到的那样,GCC允许你省略通常在macros的最后一个“固定”参数之后出现的逗号。 它还允许您在macrosreplace文本中使用##__VA_ARGS__ ,如果但前面的标记是逗号,则删除前面的逗号:

 #define debug_print(fmt, ...) \ do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0) 

这个解决scheme保留了在格式之后接受可选参数的同时需要format参数的好处。

Clang对GCC兼容性也支持这种技术。


为什么do-while循环?

这里do while的目的是do while

你想能够使用这个macros,所以它看起来像一个函数调用,这意味着它将后面跟一个分号。 因此,你必须打包macros观体适合。 如果你使用一个没有周围do { ... } while (0)语句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); 

不幸的是,缩进并不能反映stream程的实际控制,因为预处理器产生的代码等同于此(缩进和加上大括号以强调实际含义):

 if (x > y) { if (DEBUG) fprintf(stderr, "x (%d) > y (%d)\n", x, y); else do_something_useful(x, y); } 

macros观上的下一次尝试可能是:

 /* 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)循环避免了这两个问题。

还有另外一种写这个macros的方法可能会起作用:

 /* BAD - BAD - BAD */ #define debug_print(...) \ ((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0)) 

这使程序片段显示为有效。 (void)强制转换防止它在需要值的上下文中使用 – 但它可以用作do { ... } while (0)版本不能的逗号运算符的左操作数。 如果你认为你应该能够将debugging代码embedded到这样的expression式中,那么你可能更喜欢这个。 如果您希望debugging打印作为完整的语句,那么do { ... } while (0)版本更好。 请注意,如果macros的主体涉及任何分号(粗略地说),那么只能使用do { ... } while(0)表示法。 它总是有效的; expression陈述机制可能更难以应用。 您也可能会从编译器中获得您希望避免的expressionforms的警告; 它将取决于您使用的编译器和标志。


TPOP之前在http://plan9.bell-labs.com/cm/cs/tpop和http://cm.bell-labs.com/cm/cs/tpop,但现在都是(2015-08-10)破碎。;

我使用这样的东西:

 #ifdef DEBUG #define D if(1) #else #define D if(0) #endif 

比我只使用D作为前缀:

 D printf("x=%0.3f\n",x); 

编译器看到的debugging代码,没有逗号问题,它在任何地方工作。 当printf不够用的时候,它也可以工作,比如说你必须转储一个数组或者计算一些对程序本身来说是多余的诊断值。

编辑:好吧,它可能会产生一个问题,当有else地方可以截获这个注入if 。 这是一个超越它的版本:

 #ifdef DEBUG #define D #else #define D for(;0;) #endif 

对于便携式(ISO C90)实现,可以使用双括号,

 #include <stdio.h> #include <stdarg.h> #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 <stdio.h> #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 DEBUG #define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) #else #define debug_print(fmt, ...) do {} while (0) #endif 

我认为这是更清洁。

这是我使用的版本:

 #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 
 #define debug_print(FMT, ARGS...) do { \ if (DEBUG) \ fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \ } while (0) 

根据http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html ,在__VA_ARGS__之前应该有一个##

否则,macros#define dbg_print(format, ...) printf(format, __VA_ARGS__)将不会编译以下示例: dbg_print("hello world");

这是我使用的:

 #if DBG #include <stdio.h> #define DBGPRINT printf #else #define DBGPRINT(...) /**/ #endif 

即使没有额外的参数,它也能很好地处理printf。 在DBG == 0的情况下,即使最笨的编译器也没有什么咀嚼,所以不会生成代码。

我最喜欢的是var_dump ,当它被调用时:

var_dump("%d", count);

产生如下输出:

patch.c:150:main(): count = 0

感谢@“Jonathan Leffler”。 都是C89开心:

 #define DEBUG 1 #include <stdarg.h> #include <stdio.h> void debug_vprintf(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); } /* Call as: (DOUBLE PARENTHESES ARE MANDATORY) */ /* var_debug(("outfd = %d, somefailed = %d\n", outfd, somefailed)); */ #define var_debug(x) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \ __FILE__, __LINE__, __func__); debug_vprintf x; }} while (0) /* var_dump("%s" variable_name); */ #define var_dump(fmt, var) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \ __FILE__, __LINE__, __func__); debug_vprintf ("%s = " fmt, #var, var); }} while (0) #define DEBUG_HERE do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): HERE\n", \ __FILE__, __LINE__, __func__); }} while (0) 

我已经炖了好多年了,最近才看到这个问题才提出这个解决scheme。 首先,与Leffler的回答不同,我没有看到他的观点,即debugging打印应该总是被编译。 是的,你最终得到的是不能编译的debugging版本,但是在完成一个项目之前编译和testing并不困难。 假设你没有。 发生的最糟糕的事情就是你必须纠正一个大项目中的几个问题。 如果你的项目不是HUUUUGE,那么比这更容易。

我的解决scheme提供了debugging详细程度; 如果将其设置为最高级,则全部编译。 如果您最近一直在使用较高的debugging细节级别,那么他们可能都已经编译完成了。 我从来没有需要超过三个级别,但乔纳森说,他已经用了九个。 他的方法同样可以具有debugging打印细节的级别。 矿可以很容易地扩展到支持任何级别,他可以支持任何级别。 我的方法的使用可能更简单; 在代码中使用时只需要两条语句(尽pipe我也使用了第三条语句)。

在testing成本之前,不要花费额外的时间来看看它们在交付之前是否会进行编译

  1. 你必须相信他们才能被优化出来,如果你有足够的优化级别,那么就会发生这种情况。
  2. 而且,如果你为了testing的目的closures了优化,那么他们可能不会这样做(这是公认的罕见的)。 在debugging期间几乎肯定不会,因此在运行时会执行数十或数百个“if(DEBUG)”语句; 从而减缓执行(这是我的原则反对),并不重要的是,增加您的可执行文件或DLL大小; 因此执行和编译时间。 乔纳森,但是,通知我他的方法可以使也不会编译语句。

在现代预取处理器中,分支实际上是相当昂贵的。 如果你的应用程序不是一个时间关键的应用程序可能不是一个大问题; 但是,如果性能是一个问题,那么,是的,一个足够大的交易,我宁愿select更快执行的debugging代码(可能更快的版本,在极less数情况下,如上所述)。

所以,我想要的是一个debugging打印macros,如果不打印,它不会被编译,但如果是的话。 我也想要debugging的级别,例如,如果我希望代码的性能至关重要的部分不是在某些时候打印,而是要打印到别人,我可以设置一个debugging级别,并有额外的debugging打印。遇到了一个方法来实现debugging级别,确定如果打印甚至编译或不。 我这样做了:

DebugLog.h:

 // FILE: DebugLog.h // REMARKS: This is a generic pair of files useful for debugging. It provides three levels of // debug logging, currently; in addition to disabling it. Level 3 is the most information. // Levels 2 and 1 have progressively more. Thus, you can write: // DEBUGLOG_LOG(1, "a number=%d", 7); // and it will be seen if DEBUG is anything other than undefined or zero. If you write // DEBUGLOG_LOG(3, "another number=%d", 15); // it will only be seen if DEBUG is 3. When not being displayed, these routines compile // to NOTHING. I reject the argument that debug code needs to always be compiled so as to // keep it current. I would rather have a leaner and faster app, and just not be lazy, and // maintain debugs as needed. I don't know if this works with the C preprocessor or not, // but the rest of the code is fully C compliant also if it is. #define DEBUG 1 #ifdef DEBUG #define DEBUGLOG_INIT(filename) debuglog_init(filename) #else #define debuglog_init(...) #endif #ifdef DEBUG #define DEBUGLOG_CLOSE debuglog_close #else #define debuglog_close(...) #endif #define DEBUGLOG_LOG(level, fmt, ...) DEBUGLOG_LOG ## level (fmt, ##__VA_ARGS__) #if DEBUG == 0 #define DEBUGLOG_LOG0(...) #endif #if DEBUG >= 1 #define DEBUGLOG_LOG1(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__) #else #define DEBUGLOG_LOG1(...) #endif #if DEBUG >= 2 #define DEBUGLOG_LOG2(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__) #else #define DEBUGLOG_LOG2(...) #endif #if DEBUG == 3 #define DEBUGLOG_LOG3(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__) #else #define DEBUGLOG_LOG3(...) #endif void debuglog_init(char *filename); void debuglog_close(void); void debuglog_log(char* format, ...); 

DebugLog.cpp:

 // FILE: DebugLog.h // REMARKS: This is a generic pair of files useful for debugging. It provides three levels of // debug logging, currently; in addition to disabling it. See DebugLog.h's remarks for more // info. #include <stdio.h> #include <stdarg.h> #include "DebugLog.h" FILE *hndl; char *savedFilename; void debuglog_init(char *filename) { savedFilename = filename; hndl = fopen(savedFilename, "wt"); fclose(hndl); } void debuglog_close(void) { //fclose(hndl); } void debuglog_log(char* format, ...) { hndl = fopen(savedFilename,"at"); va_list argptr; va_start(argptr, format); vfprintf(hndl, format, argptr); va_end(argptr); fputc('\n',hndl); fclose(hndl); } 

使用macros

要使用它,只需要:

 DEBUGLOG_INIT("afile.log"); 

要写入日志文件,只需:

 DEBUGLOG_LOG(1, "the value is: %d", anint); 

要closures它,你可以这样做:

 DEBUGLOG_CLOSE(); 

尽pipe目前这还不是必要的,从技术上来说,因为它什么也不做。 但是,我现在还在使用CLOSE,以防万一我改变了主意,并希望在logging语句之间打开文件。

然后,当你想打开debugging打印,只需编辑头文件中的第一个#define来说,例如

 #define DEBUG 1 

要将日志语句编译为无,请执行

 #define DEBUG 0 

如果你需要从一个经常执行的代码(即高级别的细节)获得信息,你可能想写:

  DEBUGLOG_LOG(3, "the value is: %d", anint); 

如果将DEBUG定义为3,则编译日志级别1,2和3。 如果将其设置为2,则会获得日志级别1和2.如果将其设置为1,则只会获得日志级别1语句。

至于do-while循环,因为它的计算结果是单个函数或者什么也不是,而不是if语句,所以不需要循环。 好吧,嘲笑我使用C而不是C ++ IO(和Qt的QString :: arg()是一种更安全的格式化Qt中的variables的方式 – 这是非常光滑的,但需要更多的代码和格式文档不像组织因为它可能是 – 但我仍然发现它的情况下),但是你可以把任何代码放在你想要的.cpp文件中。 它也可能是一个类,但是然后你需要实例化它并跟上它,或者做一个新的()并将其存储。 这样,只要将#include,init和可选的close语句放到源代码中,就可以开始使用它了。 如果你是这么倾向的话,那将会成为一个好class级。

我以前见过很多解决scheme,但是没有一个适合我的标准以及这个。

  1. 它可以扩展到尽可能多的水平,只要你喜欢。
  2. 如果不打印,它将编译为无。
  3. 它将IO集中在一个易于编辑的地方。
  4. 这是灵活的,使用printf格式。

此外,

  1. 它不需要黑客打印没有参数(例如ERRLOG_LOG(3, "got here!"); ); 从而允许你使用,例如Qt的安全.arg()格式。 它适用于MSVC,因此,可能gcc。 它在#define使用## ,这是Leffler指出的非标准的,但是被广泛支持。 (你可以重新编码,如果有必要,不要使用## ,但是你必须使用他提供的黑客)。

警告:如果您忘记提供日志logging级别参数,MSVC不会帮助标识符未定义。

您可能需要使用除DEBUG以外的预处理器符号名称,因为某些源代码也定义了该符号(例如,使用./configure命令编制progs以准备构build)。 当我开发它时,这似乎很自然。 我在一个应用程序中使用其他的东西来开发它,并且将日志打印发送到一个文件更为方便; 但将其更改为vprintf()也可以正常工作。

我希望这可以节省你们很多关于搞清楚debugging日志logging的最佳方法的悲伤; 或者向你展示你可能更喜欢的一个。 我已经半心半意地试图找出这个问题数十年了。 在MSVC 2012和2015工作,因此可能在海湾合作委员会; 以及可能在其他许多人身上工作,但我没有在他们身上进行testing。

我相信这个主题的变化给出了debugging类别,不需要每个类别都有一个单独的macros名称。

我在一个Arduino项目中使用了这种变化,程序空间被限制在32K,dynamic内存被限制在2K。 debugging语句和跟踪debuggingstring的添加很快占用了空间。 因此,每次构build代码时,能够将编译时包含的debugging跟踪限制到最低限度是非常重要的。

debug.h

 #ifndef DEBUG_H #define DEBUG_H #define PRINT(DEBUG_CATEGORY, VALUE) do { if (DEBUG_CATEGORY & DEBUG_MASK) Serial.print(VALUE);} while (0); #endif 

调用.cpp文件

 #define DEBUG_MASK 0x06 #include "Debug.h" ... PRINT(4, "Time out error,\t"); ...