尝试创建定义DEBUG时可用于打印调试消息的宏,例如以下伪代码:
#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)
如何使用宏完成此操作?
尝试创建定义DEBUG时可用于打印调试消息的宏,例如以下伪代码:
#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)
如何使用宏完成此操作?
Answers:
#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感到困惑,并且没有有用的编译器扩展,则没有一种特别干净的方法来处理它。我过去使用的技术是:
#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)
然后,在代码中编写:
TRACE(("message %d\n", var));
双括号至关重要-这就是为什么在宏扩展中使用有趣的符号的原因。和以前一样,编译器总是检查代码的语法有效性(这很好),但是优化器仅在DEBUG宏的计算结果为非零时才调用打印功能。
这确实需要支持功能(在示例中为dbg_printf())来处理“ stderr”之类的事情。它要求您知道如何编写varargs函数,但这并不难:
#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章(另请参见TPOP上的Wikipedia)。
这是“到那里,就那样做”的经验-我本质上使用了其他答案中描述的技术,其中非调试版本多年来(超过十年)都看不到类似于printf的语句。但是我遇到了TPOP中的建议(请参阅我以前的评论),然后在几年后确实启用了一些调试代码,并且遇到了上下文更改中断调试的问题。好几次,始终对打印进行验证使我免于以后的麻烦。
我使用NDEBUG仅控制断言,并使用单独的宏(通常是DEBUG)来控制是否在程序中内置了调试跟踪。即使内置了调试跟踪,我也经常不希望调试输出无条件显示,因此我有一种机制来控制输出是否出现(调试级别,而不是fprintf()
直接调用,而是调用仅有条件打印的调试打印功能)因此根据程序选项可以打印或不打印相同版本的代码)。对于大型程序,我还有一个代码的“多个子系统”版本,因此我可以在运行时控制下,使程序的不同部分产生不同数量的跟踪。
我主张对于所有构建,编译器都应查看诊断语句。但是,除非启用了debug,否则编译器不会为调试跟踪语句生成任何代码。基本上,这意味着您每次编译时都会检查编译器的所有代码-是发布还是调试。这是一件好事!
/*
@(#)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 */
#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 */
凯尔·勃兰特(Kyle Brandt)问:
无论如何,
debug_print
即使没有参数,它仍然可以工作吗?例如:debug_print("Foo");
有一个简单的老式hack:
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)破碎。
如果您好奇,可以在GitHub的SOQ(堆栈溢出问题)存储库中以文件形式查看此代码debug.c
,debug.h
并mddebug.c
在
src / libsoq
子目录中查看。
#define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
__FILE__, __LINE__, __func__, __VA_ARGS__
如果您没有printf参数,即如果您仅调用,它将不会编译。debug_print("Some msg\n");
您可以使用fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);
## __ VA_ARGS__ 来解决此问题。
#define debug_print(fmt, ...)
和之间的区别#define debug_print(...)
。其中第一个需要至少一个参数,格式字符串(fmt
)和零个或多个其他参数;第二个总共需要零个或多个参数。如果debug_print()
与第一个一起使用,则会从预处理器收到有关滥用宏的错误,而第二个则不会。但是,由于替换文本不是有效的C,您仍然会遇到编译错误。因此,它的确没有太大区别-因此使用了术语“有限检查”。
-D input=4,macros=9,rules=2
将输入系统的调试级别设置为4,将宏系统的调试级别设置为9(经过严格的审查) )和2的规则系统。主题有无尽的变化;使用适合您的任何东西。
我用这样的东西:
#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
for(;0;)
,当您编写类似D continue;
或的东西时,它可能会产生问题D break;
。
对于可移植(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 debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug_print(fmt, ...) do {} while (0)
#endif
我认为这比较干净。
assert()
来自stdlib的工作方式相同,我通常只是将NDEBUG
宏重新用于自己的调试代码...
根据http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html,应该有一个##
before __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)
我最喜欢的是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)
因此,使用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
然后,您可以分析对什么表达式进行了评估。
它可以防止双重评估问题,但是缺少gensyms确实会使它容易受到名称冲突的影响。
但是它确实嵌套:
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作为变量名,就可以了。
当然,我发现它(以及字符串的关联版本和调试级别的版本等)非常有价值。
多年来,我一直在思考如何执行此操作,最后提出了一个解决方案。但是,我不知道这里已经有其他解决方案了。首先,与Leffler的回答不同,我看不到他关于调试打印应该始终进行编译的说法。在不需要测试的情况下,我宁愿不要在我的项目中执行大量不需要的代码,以防我需要测试并且可能没有对它们进行优化。
每次都不编译可能听起来比实际更糟。您确实会得到有时不会编译的调试打印,但是在完成项目之前,对其进行编译和测试并不难。在此系统中,如果您使用的是三个调试级别,则只需将其放在调试消息级别为三个级别,修复编译错误并在完成代码之前检查其他任何错误。(当然,由于调试语句的编译不能保证它们仍按预期运行。)
我的解决方案还提供了调试详细信息的级别;如果将其设置为最高级别,它们都会编译。如果您最近一直在使用较高的调试详细信息级别,那么它们当时都可以编译。最终更新应该很容易。我从来不需要超过三个级别,但乔纳森说他已经使用了九个级别。这种方法(像Leffler方法一样)可以扩展到任意多个级别。我方法的使用可能更简单;在代码中使用时仅需要两个语句。但是,我也正在编码CLOSE宏-尽管它没有任何作用。如果我要发送到文件,则可能会出现。
相对于成本,测试它们以确保它们可以在交付之前进行编译的额外步骤是:
实际上,在现代的预取处理器中,分支的成本相对较高。如果您的应用程序不是时间紧迫的应用程序,那么可能并不重要。但是如果性能是一个问题,那么是的,我想选择执行速度更快的调试代码(在少数情况下,可能会发布得更快,在极少数情况下,如上所述)就足够了。
因此,我想要的是一个调试打印宏,如果不进行打印,它不会编译,而如果不打印,它会编译。我还需要调试级别,例如,如果我希望代码中对性能至关重要的部分在某些时候不打印,而在其他时间打印,则可以设置调试级别,并增加额外的调试打印。遇到一种实现调试级别的方法,该级别确定打印是否已编译。我是这样实现的:
// 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, ...);
// 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中使用Qt的QString :: arg()也是一种更安全的格式化变量的方式-它很漂亮,但是需要更多的代码,并且格式化文档的组织性不强可能会-但仍然可以找到更适合的情况),但是您可以将所需的任何代码放入.cpp文件中。它也可能是一个类,但随后您需要实例化它并跟上它,或者执行new()并将其存储。这样,您只需将#include,init和可选的close语句放入源中,就可以开始使用它了。但是,如果您愿意的话,这将是一个很好的选择。
我以前见过很多解决方案,但没有一个比我的标准更适合我的标准了。
并不十分重要,但另外:
DEBUGLOG_LOG(3, "got here!");
);因此允许您使用,例如Qt的更安全的.arg()格式。它适用于MSVC,因此可能适用于gcc。正如Leffler指出的那样,它##
在#define
s中使用,它是非标准的,但得到了广泛的支持。(您可以重新编码它,使其##
在必要时不使用,但您必须使用他提供的技巧。)警告:如果您忘记提供日志记录级别参数,则MSVC会毫无帮助地声明未定义标识符。
您可能要使用除DEBUG以外的预处理器符号名称,因为某些来源也定义了该符号(例如,使用 ./configure
命令进行编译的)。对我来说,开发它似乎很自然。我是在DLL被其他人使用的应用程序中开发的,将日志打印文件发送到文件更方便。但是将其更改为vprintf()也可以正常工作。
我希望这可以避免许多人为找出调试日志的最佳方法而感到悲伤。或向您显示您可能更喜欢的一种。数十年来,我一直全心全意地试图弄清楚这一点。适用于MSVC 2012和2015,因此可能适用于gcc;以及可能在许多其他产品上工作,但我尚未在它们上进行测试。
我的意思是也要制作这一天的流媒体版本。
注意:感谢Leffler,他诚挚地帮助我为StackOverflow设置了更好的消息格式。
if (DEBUG)
在运行时执行数十或数百条语句,而这些语句不会得到优化”-这在风车中是很明显的。我所描述的系统的全部要点是,代码是由编译器检查的(重要且自动的-不需要特殊的构建),但调试代码完全不生成,因为它已被优化(因此对运行时的影响为零)代码大小或性能,因为代码在运行时不存在。
((void)0)
-这很容易。
我相信主题的这种变化形式提供了调试类别,而无需为每个类别使用单独的宏名称。
我在一个Arduino项目中使用了这种变体,其中程序空间限制为32K,动态内存限制为2K。添加调试语句和跟踪调试字符串会很快占用空间。因此,至关重要的是,每次编译代码时,都必须将编译时包含的调试跟踪限制为所需的最少数量。
#ifndef DEBUG_H
#define DEBUG_H
#define PRINT(DEBUG_CATEGORY, VALUE) do { if (DEBUG_CATEGORY & DEBUG_MASK) Serial.print(VALUE);} while (0);
#endif
#define DEBUG_MASK 0x06
#include "Debug.h"
...
PRINT(4, "Time out error,\t");
...