#define 宏用于 C 中的调试打印?

发布于 2024-08-09 06:28:44 字数 167 浏览 4 评论 0原文

尝试创建一个可在定义 DEBUG 时用于打印调试消息的宏,如以下伪代码:

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

这是如何用宏完成的?

Trying to create a macro which can be used for print debug messages when DEBUG is defined, like the following pseudo code:

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

How is this accomplished with a macro?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

无所谓啦 2024-08-16 06:28:44

如果您使用 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_TEST 我使用 DEBUG 的地方。

如果您坚持使用字符串文字作为格式字符串(无论如何可能是个好主意),您还可以引入诸如 __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 的调试语句。这对于长期代码(将持续一两年的代码)非常重要。

假设一段代码多年来一直处于休眠(稳定)状态,但现在需要更改。您重新启用调试跟踪 - 但必须调试调试(跟踪)代码是令人沮丧的,因为它引用了在稳定维护期间已重命名或重新键入的变量。如果编译器(后预处理器)始终看到打印语句,则它可以确保任何周围的更改不会使诊断无效。如果编译器没有看到打印语句,它无法保护您免受自己的粗心(或您的同事或合作者的粗心)的影响。请参阅 Kernighan 和 Pike 的“编程实践”,尤其是章节8(另请参阅维基百科上的 TPOP)。

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

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

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

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 或更高版本的单参数变体

Kyle Brandt 问:

无论如何,即使没有参数,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)

此解决方案保留了要求格式参数同时接受格式后面的可选参数的优点。

Clang 也支持此技术以实现 GCC 兼容性。


C23 和 __VA_OPT__

C++20(及更高版本)和 C23(及更高版本)都添加了 __VA_OPT__ 机制来处理 __VA_ARGS__ 的问题由 GCC 使用 , ## __VA_ARGS__ 表示法处理。

您只需使用:

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

如果 __VA_ARGS__ 不为空,则将 __VA_OPT__ 的参数添加到输出中(如果 __VA_ARGS__ 为空,则不添加任何内容)。如果 __STDC_VERSION__ >= 202311L 则应该可用 - 但请注意,在使用 -std= 编译时,GCC 14.1.0(仍然)设置 __STDC_VERSION__ == 202000 c23-std=iso9899:2024


为什么要使用 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);

不幸的是,缩进不' t 反映流程的实际控制,因为预处理器生成与此等效的代码(添加缩进和大括号以强调实际含义):

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 was previously at http://plan9.bell-labs.com/cm/cs/tpop and http://cm.bell-labs.com/cm/cs/tpop but both are now (2015-08-10) broken._


GitHub 中的代码

如果您好奇,可以在我的 SOQ (Stack
溢出问题)存储库作为文件 debug.cdebug.hmddebug.cmddebug.h 中这
src/libsoq
子目录。

If you use a C99 or later compiler

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

It assumes you are using C99 (the variable argument list notation is not supported in earlier versions). The do { ... } while (0) idiom ensures that the code acts like a statement (function call). The unconditional use of the code ensures that the compiler always checks that your debug code is valid — but the optimizer will remove the code when DEBUG is 0.

If you want to work with #ifdef DEBUG, then change the test condition:

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

And then use DEBUG_TEST where I used DEBUG.

If you insist on a string literal for the format string (probably a good idea anyway), you can also introduce things like __FILE__, __LINE__ and __func__ into the output, which can improve the diagnostics:

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

This relies on string concatenation to create a bigger format string than the programmer writes.

If you use a C89 compiler

If you are stuck with C89 and no useful compiler extension, then there isn't a particularly clean way to handle it. The technique I used to use was:

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

And then, in the code, write:

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

The double-parentheses are crucial — and are why you have the funny notation in the macro expansion. As before, the compiler always checks the code for syntactic validity (which is good) but the optimizer only invokes the printing function if the DEBUG macro evaluates to non-zero.

This does require a support function — dbg_printf() in the example — to handle things like 'stderr'. It requires you to know how to write varargs functions, but that isn't hard:

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

You can also use this technique in C99, of course, but the __VA_ARGS__ technique is neater because it uses regular function notation, not the double-parentheses hack.

Why is it crucial that the compiler always see the debug code?

[Rehashing comments made to another answer.]

One central idea behind both the C99 and C89 implementations above is that the compiler proper always sees the debugging printf-like statements. This is important for long-term code — code that will last a decade or two.

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

This is 'been there, done that' experience — I used essentially the technique described in other answers where the non-debug build does not see the printf-like statements for a number of years (more than a decade). But I came across the advice in TPOP (see my previous comment), and then did enable some debugging code after a number of years, and ran into problems of changed context breaking the debugging. Several times, having the printing always validated has saved me from later problems.

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.

I am advocating that for all builds, the compiler should see the diagnostic statements; however, the compiler won't generate any code for the debugging trace statements unless debug is enabled. Basically, it means that all of your code is checked by the compiler every time you compile - whether for release or debugging. This is a good thing!

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 
quot;;
#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 */

Single argument variant for C99 or later

Kyle Brandt asked:

Anyway to do this so debug_print still works even if there are no arguments? For example:

    debug_print("Foo");

There's one simple, old-fashioned hack:

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

The GCC-only solution shown below also provides support for that.

However, you can do it with the straight C99 system by using:

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

Compared to the first version, you lose the limited checking that requires the 'fmt' argument, which means that someone could try to call 'debug_print()' with no arguments (but the trailing comma in the argument list to fprintf() would fail to compile). Whether the loss of checking is a problem at all is debatable.

GCC-specific technique for a single argument

Some compilers may offer extensions for other ways of handling variable-length argument lists in macros. Specifically, as first noted in the comments by Hugo Ideler, GCC allows you to omit the comma that would normally appear after the last 'fixed' argument to the macro. It also allows you to use ##__VA_ARGS__ in the macro replacement text, which deletes the comma preceding the notation if, but only if, the previous token is a comma:

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

This solution retains the benefit of requiring the format argument while accepting optional arguments after the format.

This technique is also supported by Clang for GCC compatibility.


C23 and __VA_OPT__

Both C++20 (and later) and C23 (and later) add the __VA_OPT__ mechanism to handle the problem with __VA_ARGS__ that is handled by GCC using the , ## __VA_ARGS__ notation.

You simply use:

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

The argument to __VA_OPT__ is added to the output if __VA_ARGS__ is not empty (and nothing is added if __VA_ARGS__ is empty). This should be available if __STDC_VERSION__ >= 202311L — but be aware that GCC 14.1.0 (still) sets __STDC_VERSION__ == 202000 when compiling with -std=c23 or -std=iso9899:2024.


Why the do-while loop?

What's the purpose of the do while here?

You want to be able to use the macro so it looks like a function call, which means it will be followed by a semi-colon. Therefore, you have to package the macro body to suit. If you use an if statement without the surrounding do { ... } while (0), you will have:

/* BAD - BAD - BAD */
#define debug_print(...) \
            if (DEBUG) fprintf(stderr, __VA_ARGS__)

Now, suppose you write:

if (x > y)
    debug_print("x (%d) > y (%d)\n", x, y);
else
    do_something_useful(x, y);

Unfortunately, that indentation doesn't reflect the actual control of flow, because the preprocessor produces code equivalent to this (indented and braces added to emphasize the actual meaning):

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

The next attempt at the macro might be:

/* BAD - BAD - BAD */
#define debug_print(...) \
            if (DEBUG) { fprintf(stderr, __VA_ARGS__); }

And the same code fragment now produces:

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

And the else is now a syntax error. The do { ... } while(0) loop avoids both these problems.

There's one other way of writing the macro which might work:

/* 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 was previously at http://plan9.bell-labs.com/cm/cs/tpop and http://cm.bell-labs.com/cm/cs/tpop but both are now (2015-08-10) broken._


Code in GitHub

If you're curious, you can look at this code in GitHub in my SOQ (Stack
Overflow Questions) repository as files debug.c, debug.h, mddebug.c and mddebug.h in the
src/libsoq
sub-directory.

雾里花 2024-08-16 06:28:44

我使用这样的东西:

#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

I use something like this:

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

Than I just use D as a prefix:

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

Compiler sees the debug code, there is no comma problem and it works everywhere. Also it works when printf is not enough, say when you must dump an array or calculate some diagnosing value that is redundant to the program itself.

EDIT: Ok, it might generate a problem when there is else somewhere near that can be intercepted by this injected if. This is a version that goes over it:

#ifdef DEBUG
 #define D 
#else
 #define D for(;0;)
#endif
你怎么这么可爱啊 2024-08-16 06:28:44

这是我使用的版本:

#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

Here's the version I use:

#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
浴红衣 2024-08-16 06:28:44

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

For a portable (ISO C90) implementation, you could use double parentheses, like this;

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

or (hackish, wouldn't recommend it)

#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;
}
‘画卷フ 2024-08-16 06:28:44

我会做一些

#ifdef DEBUG
#define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug_print(fmt, ...) do {} while (0)
#endif

我认为这样更干净的事情。

I would do something like

#ifdef DEBUG
#define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug_print(fmt, ...) do {} while (0)
#endif

I think this is cleaner.

木落 2024-08-16 06:28:44

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

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

According to http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html,
there should be a ## before __VA_ARGS__.

Otherwise, a macro #define dbg_print(format, ...) printf(format, __VA_ARGS__) will not compile the following example: dbg_print("hello world");.

爱的故事 2024-08-16 06:28:44
#define debug_print(FMT, ARGS...) do { \
    if (DEBUG) \
        fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
    } while (0)
#define debug_print(FMT, ARGS...) do { \
    if (DEBUG) \
        fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
    } while (0)
情场扛把子 2024-08-16 06:28:44

这就是我使用的:

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

即使没有额外的参数,它也有正确处理 printf 的好处。如果 DBG ==0,即使是最愚蠢的编译器也没有什么可咀嚼的,因此不会生成任何代码。

This is what I use:

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

It has the nice benefit to handle printf properly, even without additional arguments. In case DBG ==0, even the dumbest compiler gets nothing to chew upon, so no code is generated.

森林迷了鹿 2024-08-16 06:28:44

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

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

So, when using gcc, I like:

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

Because it can be inserted into code.

Suppose you're trying to debug

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

720

Then you can change it to:

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

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

And you can get an analysis of what expression was evaluated to what.

It's protected against the double-evaluation problem, but the absence of gensyms does leave it open to name-collisions.

However it does nest:

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

So I think that as long as you avoid using g2rE3 as a variable name, you'll be OK.

Certainly I've found it (and allied versions for strings, and versions for debug levels etc) invaluable.

写给空气的情书 2024-08-16 06:28:44

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

My favourite of the below is var_dump, which when called as:

var_dump("%d", count);

produces output like:

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

Credit to @"Jonathan Leffler". All are C89-happy:

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)
南街九尾狐 2024-08-16 06:28:44

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

如果您需要来自频繁执行的代码段(即高级代码)的信息,请执行以下操作: 如果您将 DEBUG 定义为 3,则

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

日志记录级别为 1、2 和 1。 3 编译。如果将其设置为 2,您将获得日志记录级别 1 和 2。 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。正如 Leffler 指出的那样,它在 #define 中使用 ##,这是非标准的,但得到了广泛支持。 (如有必要,您可以重新编码以不使用 ##,但您必须使用他提供的 hack。)

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

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

我希望这可以减轻你们中的许多人在找出进行调试日志记录的最佳方法时的烦恼;或者向您展示您可能更喜欢的一个。几十年来我一直半心半意地试图解决这个问题。工作于MSVC 2012 & 2012 2015 年,因此可能在 gcc 上;以及可能对许多其他人进行的工作,但我还没有在他们身上进行过测试。

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

注意:感谢 Leffler,他热诚地帮助我更好地为 StackOverflow 格式化我的消息。

I've been stewing on how to do this for years, and finally come up with a solution. However, I didn't know that there were other solutions here already. First, at difference with Leffler's answer, I don't see his argument that debug prints should always be compiled. I'd rather not have tons of unneeded code executing in my project, when not needed, in cases where I need to test and they might not be getting optimized out.

Not compiling every time might sound worse than it is in actual practice. You do wind up with debug prints that don't compile sometimes, but it's not so hard to compile and test them before finalizing a project. With this system, if you are using three levels of debugs, just put it on debug message level three, fix your compile errors and check for any others before you finalize yer code. (Since of course, debug statements compiling are no guarantee that they are still working as intended.)

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.

Against the cost the extra step of testing them to see that they will compile before delivery, is that

  1. You must trust them to get optimized out, which admittedly SHOULD happen if you have a sufficient optimization level.
  2. 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.

Branches are actually relatively pretty costly in modern pre-fetching processors. Maybe not a big deal if your app is not a time-critical one; but if performance is an issue, then, yes, a big enough deal that I'd prefer to opt for somewhat faster-executing debug code (and possibly faster release, in rare cases, as noted).

So, what I wanted is a debug print macro that does not compile if it is not to be printed, but does if it is. I also wanted levels of debugging, so that, e.g. if I wanted performance-crucial parts of the code not to print at some times, but to print at others, I could set a debug level, and have extra debug prints kick in. I came across a way to implement debug levels that determined if the print was even compiled or not. I achieved it this way:

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

Using the macros

To use it, just do:

DEBUGLOG_INIT("afile.log");

To write to the log file, just do:

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

To close it, you do:

DEBUGLOG_CLOSE();

although currently this isn't even necessary, technically speaking, as it does nothing. I'm still using the CLOSE right now, however, in case I change my mind about how it works, and want to leave the file open between logging statements.

Then, when you want to turn on debug printing, just edit the first #define in the header file to say, e.g.

#define DEBUG 1

To have logging statements compile to nothing, do

#define DEBUG 0

If you need info from a frequently executed piece of code (i.e. a high level of detail), you may want to write:

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

If you define DEBUG to be 3, logging levels 1, 2 & 3 compile. If you set it to 2, you get logging levels 1 & 2. If you set it to 1, you only get logging level 1 statements.

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.

I'd previously seen a lot of solutions, but none suited my criteria as well as this one.

  1. It can be extended to do as many levels as you like.
  2. It compiles to nothing if not printing.
  3. It centralizes IO in one easy-to-edit place.
  4. It's flexible, using printf formatting.
  5. 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.

Not terribly significant, but in addition:

  1. It requires no hack to print without arguments (e.g. DEBUGLOG_LOG(3, "got here!");); thus allowing you to use, e.g. Qt's safer .arg() formatting. It works on MSVC, and thus, probably gcc. It uses ## in the #defines, which is non-standard, as Leffler points out, but is widely supported. (You can recode it not to use ## if necessary, but you will have to use a hack such as he provides.)

Warning: If you forget to provide the logging level argument, MSVC unhelpfully claims the identifier is not defined.

You might want to use a preprocessor symbol name other than DEBUG, as some source also defines that symbol (eg. progs using ./configure commands to prepare for building). It seemed natural to me when I developed it. I developed it in an application where the DLL is being used by something else, and it's more convent to send log prints to a file; but changing it to vprintf() would work fine, too.

I hope this saves many of you grief about figuring out the best way to do debug logging; or shows you one you might prefer. I've half-heartedly been trying to figure this one out for decades. Works in MSVC 2012 & 2015, and thus probably on gcc; as well as probably working on many others, but I haven't tested it on them.

I mean to make a streaming version of this one day, too.

Note: Thanks go to Leffler, who has cordially helped me format my message better for StackOverflow.

小…楫夜泊 2024-08-16 06:28:44
#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);
#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);
和我恋爱吧 2024-08-16 06:28:44

我相信主题的这种变体提供了调试类别,而无需每个类别都有单独的宏名称。

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

I believe this variation of the theme gives debug categories without the need to have a separate macro name per category.

I used this variation in an Arduino project where program space is limited to 32K and dynamic memory is limited to 2K. The addition of debug statements and trace debug strings quickly uses up space. So it is essential to be able to limit the debug trace that is included at compile time to the minimum necessary each time the code is built.

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

calling .cpp file

#define DEBUG_MASK 0x06
#include "Debug.h"

...
PRINT(4, "Time out error,\t");
...
偏闹i 2024-08-16 06:28:44

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

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

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

If you don't care that the output goes to stdout, you can use this:

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

trace("whatever %d, %i\n", arg1, arg2);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文