#在C中定义打印调试宏?

试图创建一个宏,可以用于在定义debug时打印调试消息,就像下面的伪代码:

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

这是如何用宏来完成的呢?

248788 次浏览

如果使用C99或更高版本的编译器

#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的地方使用DEBUG_TEST。

如果你坚持使用字符串字面值作为格式字符串(可能是个好主意),你也可以在输出中引入诸如__FILE____LINE____func__之类的东西,这可以改善诊断:

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

这依赖于字符串连接来创建一个比程序员编写的更大的格式字符串。

如果你使用C89编译器

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

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

然后,在代码中,写:

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

双括号是至关重要的—这就是为什么在宏观展开中有有趣的符号。像以前一样,编译器总是检查代码的语法有效性(这很好),但优化器只在DEBUG宏的计算结果为非零时调用打印函数。

这确实需要一个支持函数—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__技术更简洁,因为它使用常规函数符号,而不是双括号。

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

(重复对另一个答案的评论。)

上面的C99和C89实现背后的一个核心思想是,编译器本身总是能看到类似printf的调试语句。这对于长期代码来说很重要。可以持续一二十年的代码。

假设一段代码已经休眠(稳定)很多年了,但是现在需要更改。您可以重新启用调试跟踪—但是不得不调试调试(跟踪)代码是令人沮丧的,因为它引用的变量在稳定维护期间被重命名或重新键入。如果编译器(后预处理器)总是看到print语句,它将确保任何周围的更改都没有使诊断失效。如果编译器没有看到打印语句,它就不能保护您不受自己的粗心大意(或者您的同事或合作者的粗心大意)的影响。参见Kernighan和Pike的“编程实践”,特别是第8章(也可参阅Wikipedia关于TPOP的内容)。

这是“去过那里,做过那件事”的经验。我基本上使用了其他答案中描述的技术,其中非调试构建在许多年(超过十年)内都看不到类似printf的语句。但是我在TPOP中遇到了这个建议(请参阅我之前的评论),然后在几年之后启用了一些调试代码,并遇到了更改上下文破坏调试的问题。有几次,总是对打印进行验证使我避免了以后的问题。

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

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

Debug.h - version 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 - version 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的解决方案也提供了支持。

然而,你可以使用直接的C99系统,通过使用:

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

与第一个版本相比,你失去了需要'fmt'参数的有限检查,这意味着有人可以尝试在不带参数的情况下调用'debug_print()'(但fprintf()参数列表中的末尾逗号将无法编译)。检查的缺失到底是不是一个问题还有待商榷。

针对单个参数的gcc特定技术

一些编译器可能提供了在宏中处理变长参数列表的其他方法的扩展。具体来说,正如雨果•艾德尔注释中首先指出的那样,GCC允许您省略通常出现在宏的最后一个“fixed”参数之后的逗号。它还允许你在宏替换文本中使用##__VA_ARGS__,当但仅当前一个标记是逗号时,它会删除符号前面的逗号:

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

这个解决方案保留了需要format参数的好处,同时接受format后面的可选参数。

为了与GCC兼容,铿锵声也支持这种技术。


为什么是do-while循环?

这里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)符号。它总是有效的;表达式语句机制可能更难以应用。你也可能会从编译器得到你想要避免的表达式形式的警告;这取决于编译器和您使用的标志。


< p > <一口> TPOP之前在http://plan9.bell-labs.com/cm/cs/tpophttp://cm.bell-labs.com/cm/cs/tpop,但现在(2015-08-10)都被打破了。 < /一口> < / p >

GitHub中的代码

如果你好奇,你可以在GitHub中我的SOQ(堆栈 溢出问题)存储库中的文件debug.cdebug.hmddebug.c src/libsoq 子目录。< / p >
#define debug_print(FMT, ARGS...) do { \
if (DEBUG) \
fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
} while (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

对于一个可移植的(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;
}

或者(不推荐)

#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 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
根据http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html__VA_ARGS__之前应该有##

否则,宏#define dbg_print(format, ...) printf(format, __VA_ARGS__)将不会编译以下示例:

这是我所使用的:

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

即使没有额外的参数,它也具有正确处理printf的好处。在DBG ==0的情况下,即使是最笨的编译器也没有什么可考虑的,因此不会生成任何代码。

多年来,我一直在思考如何做到这一点,终于想出了一个解决方案。然而,我不知道这里已经有了其他的解。首先,与莱弗勒的回答不同的是,我没有看到他关于调试打印文件应该总是编译的论点。我不希望在我的项目中执行大量不需要的代码,在不需要的情况下,当我需要测试时,它们可能没有得到优化。

不每次都编译可能听起来比实际情况更糟糕。有时您确实会遇到无法编译的调试打印,但在完成项目之前编译和测试它们并不难。在这个系统中,如果你使用了三个级别的调试,只要把它放在调试消息级别3,在你最终完成你的代码之前修复你的编译错误并检查是否有其他错误。(当然,编译调试语句并不能保证它们仍然按预期工作。)

我的解决方案还提供了调试细节的级别;如果你将它设置为最高级别,它们都会编译。如果您最近一直在使用高调试细节级别,那么它们那时都能够编译。最终的更新应该非常简单。我从来没有需要超过3层,但乔纳森说他用了9层。这种方法(如Leffler的方法)可以扩展到任意数量的层次。使用我的方法可能更简单;在代码中使用时只需要两个语句。然而,我也在编写CLOSE宏——尽管它什么也不做。如果我要发送到文件里,可能会。

相对于成本,在交付之前测试它们以查看它们将被编译的额外步骤是

  1. 你必须相信它们会得到优化,如果你有足够的优化级别,这是应该发生的。
  2. 此外,如果你为了测试目的而关闭了优化(这种情况很少见),他们可能就不会这么做了;而且在调试期间几乎肯定不会——因此在运行时执行数十或数百个“if (debug)”语句;因此,减慢执行速度(这是我的主要反对意见),更重要的是,增加您的可执行文件或DLL大小;因此需要执行和编译时间。然而,乔纳森告诉我,他的方法也可以完全不编写语句。

在现代预取处理器中,分支实际上是相当昂贵的。如果你的应用不是一个时间紧迫的应用,这可能不是什么大问题;但是如果性能是一个问题,那么,是的,这是一个足够大的问题,我宁愿选择一些执行更快的调试代码(在极少数情况下,可能更快的发布,如前所述)。

因此,我想要的是一个调试打印宏,如果它不被打印,它就不会编译,但如果它被打印,它就会编译。我还需要调试级别,例如,如果我想让代码的关键性能部分在某些时候不打印,而是在其他时候打印,我可以设置一个调试级别,并有额外的调试打印。我遇到了一种实现调试级别的方法,它可以确定打印是否被编译。我是这样做到的:

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);
}

使用宏

要使用它,只需做:

DEBUGLOG_INIT("afile.log");

要写入日志文件,只需执行以下操作:

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

要关闭它,你需要:

DEBUGLOG_CLOSE();

虽然目前这甚至不是必要的,从技术上讲,因为它什么都不做。不过,我现在仍在使用CLOSE,以防我改变了对它的工作方式的想法,并希望在日志语句之间保持文件打开。

然后,当你想打开调试打印时,只需编辑头文件中的第一个#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中格式化变量的一种更安全的方式-它非常光滑,但需要更多的代码,格式化文档也没有那么有组织-但我仍然发现它更可取的情况),但你可以把任何代码放在你想要的.cpp文件中。它也可能是一个类,但随后需要实例化它并保持它,或者执行new()并存储它。这样,您只需将#include、init和可选的close语句放入源代码中,就可以开始使用它了。不过,如果你愿意的话,这门课倒是不错的。

我以前见过很多解决方案,但没有一个像这个一样符合我的标准。

  1. 它可以扩展到任意多的级别。
  2. 如果不打印,它将编译为零。
  3. 它将IO集中在一个易于编辑的地方。
  4. 它很灵活,使用printf格式。
  5. 同样,它不会降低调试运行速度,而始终编译的调试打印总是在调试模式下执行。如果你正在做计算机科学,而不是更容易编写信息处理,你可能会发现自己正在运行一个cpu消耗模拟器,以查看例如,调试器在哪里因为索引超出了向量的范围而停止它。它们在调试模式下运行已经非常慢了。强制执行数百个调试打印必然会进一步降低这种运行速度。对我来说,这样的跑步并不罕见。

不是特别重要,但除此之外:

  1. 它不需要破解不带参数的打印(例如DEBUGLOG_LOG(3, "got here!"););从而允许您使用,例如Qt更安全的.arg()格式。它可以在MSVC上工作,因此,可能是gcc。它在__abc2中使用##,正如Leffler指出的,这是非标准的,但得到了广泛的支持。(如果有必要,你可以重新编码,不使用##,但你必须使用他提供的黑客。)

警告:如果您忘记提供日志级别参数,MSVC会毫无帮助地声明标识符没有定义。

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

我希望这能让你们中的许多人不再为寻找调试日志的最佳方法而烦恼;或者给你看你可能更喜欢的。几十年来,我一直在半心半意地研究这个问题。工作在MSVC 2012 &2015,因此可能在gcc;可能也适用于其他很多人,但我还没有在他们身上测试过。

我也想有一天制作一个流媒体版本。

注:感谢去莱弗勒,谁已经热诚地帮助我格式我的消息更好的StackOverflow。

下面我最喜欢的是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)

我相信这个主题的变化提供了调试类别,而不需要每个类别有一个单独的宏名称。

我在Arduino项目中使用了这个变体,其中程序空间限制为32K,动态内存限制为2K。添加调试语句和跟踪调试字符串很快就会占用空间。因此,在每次构建代码时,必须能够将编译时包含的调试跟踪限制在必要的最低限度。

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");
...

所以,当使用gcc时,我喜欢:

#define DBGI(expr) ({int g2rE3=expr; fprintf(stderr, "%s:%d:%s(): ""%s->%i\n", __FILE__,  __LINE__, __func__, #expr, g2rE3); g2rE3;})

因为它可以插入到代码中。

假设您正在尝试调试

printf("%i\n", (1*2*3*4*5*6));


720

然后你可以把它改成:

printf("%i\n", DBGI(1*2*3*4*5*6));


hello.c:86:main(): 1*2*3*4*5*6->720
720

你可以得到一个表达式被求值为什么的分析。

这样就避免了双重评估的问题,但缺少物理系统确实会导致名称冲突。

然而它是嵌套的:

DBGI(printf("%i\n", DBGI(1*2*3*4*5*6)));


hello.c:86:main(): 1*2*3*4*5*6->720
720
hello.c:86:main(): printf("%i\n", DBGI(1*2*3*4*5*6))->4

所以我认为,只要避免使用g2rE3作为变量名,就可以了。

当然,我发现它(以及字符串的联合版本,以及调试级别的版本等)非常宝贵。

#define PRINT_LOG(str_format, ...) { \
time_t curtime=time (NULL); \
struct tm *ltm = localtime (&curtime); \
printf("[%d-%02d-%02d %02d:%02d:%02d] " str_format, \
ltm->tm_year + 1900, ltm->tm_mon + 1, ltm->tm_mday, \
ltm->tm_hour, ltm->tm_min, ltm->tm_sec, ##__VA_ARGS__); \
}
    

PRINT_LOG("[%d] Serving client, str=%s, number=%d\n", getpid(), "my str", 10);

如果你不关心输出到stdout,你可以使用这个:

int doDebug = DEBUG;  // Where DEBUG may be supplied in compiler command
#define trace if (doDebug) printf


trace("whatever %d, %i\n", arg1, arg2);