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

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

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


当前回答

这是我所使用的:

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

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

其他回答

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

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

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

下面是我使用的版本:

#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

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

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

My solution provides for levels of debug detail also; and if you set it to the highest level, they all compile. If you've been using a high debug detail level recently, they all were able to compile at that time. Final updates should be pretty easy. I've never needed more than three levels, but Jonathan says he's used nine. This method (like Leffler's) can be extended to any number of levels. The usage of my method may be simpler; requiring just two statements when used in your code. I am, however, coding the CLOSE macro too - although it doesn't do anything. It might if I were sending to a file.

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

You must trust them to get optimized out, which admittedly SHOULD happen if you have a sufficient optimization level. Furthermore, they probably won't if you make a release compile with optimization turned off for testing purposes (which is admittedly rare); and they almost certainly won't at all during debug - thereby executing dozens or hundreds of "if (DEBUG)" statements at runtime; thus slowing execution (which is my principle objection) and less importantly, increasing your executable or dll size; and hence execution and compile times. Jonathan, however, informs me his method can be made to also not compile statements at all.

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

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

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的语句。

As to the do-while loop, since this evaluates to either a single function or nothing, instead of an if statement, the loop is not needed. OK, castigate me for using C instead of C++ IO (and Qt's QString::arg() is a safer way of formatting variables when in Qt, too — it's pretty slick, but takes more code and the formatting documentation isn't as organized as it might be - but still I've found cases where its preferable), but you can put whatever code in the .cpp file you want. It also might be a class, but then you would need to instantiate it and keep up with it, or do a new() and store it. This way, you just drop the #include, init and optionally close statements into your source, and you are ready to begin using it. It would make a fine class, however, if you are so inclined.

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

It can be extended to do as many levels as you like. It compiles to nothing if not printing. It centralizes IO in one easy-to-edit place. It's flexible, using printf formatting. Again, it does not slow down debug runs, whereas always-compiling debug prints are always executed in debug mode. If you are doing computer science, and not easier to write information processing, you may find yourself running a CPU-consuming simulator, to see e.g. where the debugger stops it with an index out of range for a vector. These run extra-slowly in debug mode already. The mandatory execution of hundreds of debug prints will necessarily slow such runs down even further. For me, such runs are not uncommon.

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

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

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

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

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

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

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

这是我所使用的:

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

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

如果使用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的调试语句。这对于长期代码(持续十年或二十年的代码)很重要。

Suppose a piece of code has been mostly dormant (stable) for a number of years, but now needs to be changed. You re-enable debugging trace - but it is frustrating to have to debug the debugging (tracing) code because it refers to variables that have been renamed or retyped, during the years of stable maintenance. If the compiler (post pre-processor) always sees the print statement, it ensures that any surrounding changes have not invalidated the diagnostics. If the compiler does not see the print statement, it cannot protect you against your own carelessness (or the carelessness of your colleagues or collaborators). See 'The Practice of Programming' by Kernighan and Pike, especially Chapter 8 (see also Wikipedia on TPOP).

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

I use NDEBUG to control assertions only, and a separate macro (usually DEBUG) to control whether debug tracing is built into the program. Even when the debug tracing is built in, I frequently do not want debug output to appear unconditionally, so I have mechanism to control whether the output appears (debug levels, and instead of calling fprintf() directly, I call a debug print function that only conditionally prints so the same build of the code can print or not print based on program options). I also have a 'multiple-subsystem' version of the code for bigger programs, so that I can have different sections of the program producing different amounts of trace - under runtime control.

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

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特定技术

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

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

这个解决方案保留了需要format参数的好处,同时接受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))

This leaves the program fragment shown as valid. The (void) cast prevents it being used in contexts where a value is required — but it could be used as the left operand of a comma operator where the do { ... } while (0) version cannot. If you think you should be able to embed debug code into such expressions, you might prefer this. If you prefer to require the debug print to act as a full statement, then the do { ... } while (0) version is better. Note that if the body of the macro involved any semi-colons (roughly speaking), then you can only use the do { ... } while(0) notation. It always works; the expression statement mechanism can be more difficult to apply. You might also get warnings from the compiler with the expression form that you'd prefer to avoid; it will depend on the compiler and the flags you use.


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


GitHub中的代码

如果你感到好奇,你可以在我的SOQ (Stack)中查看GitHub中的这段代码 数据库中的debug.c、debug.h和mddebug.c文件 src / libsoq 子目录。