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

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

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


当前回答

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

其他回答

这是我所使用的:

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

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

多年来,我一直在思考如何做到这一点,终于想出了一个解决方案。然而,我不知道这里已经有了其他的解。首先,与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。

#define debug_print(FMT, ARGS...) do { \
    if (DEBUG) \
        fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
    } while (0)

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

var_dump(“%d”, 计数);

产生如下输出:

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

感谢@“Jonathan Leffler”。所有都是c89快乐的:

Code

#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

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

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

然而它是嵌套的:

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作为变量名,就可以了。

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