#define 宏用于 C 中的调试打印? [英] #define macro for debug printing in C?

查看:23
本文介绍了#define 宏用于 C 中的调试打印?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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?

推荐答案

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

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

它假设您使用的是 C99(早期版本不支持变量参数列表表示法).do { ... } while (0) 习惯用法确保代码的行为类似于语句(函数调用).代码的无条件使用确保编译器始终检查您的调试代码是否有效—但优化器会在 DEBUG 为 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.

如果要使用#ifdef DEBUG,则更改测试条件:

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

然后在我使用 DEBUG 的地方使用 DEBUG_TEST.

And then use DEBUG_TEST where I used DEBUG.

如果您坚持使用字符串文字作为格式字符串(无论如何可能是个好主意),您还可以引入诸如 __FILE____LINE____func__ 之类的东西 到输出中,可以改善诊断:

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.

如果您坚持使用 C89 并且没有有用的编译器扩展,那么就没有特别干净的方法来处理它.我曾经使用的技术是:

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)

然后,在代码中,写:

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

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

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.

这确实需要一个支持函数—示例中的 dbg_printf() —处理诸如stderr"之类的事情.它要求您知道如何编写可变参数函数,但这并不难:

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

当然,您也可以在 C99 中使用此技术,但 __VA_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.

[重复对另一个答案的评论.]

上述 C99 和 C89 实现背后的一个中心思想是编译器本身总是看到调试 printf 之类的语句.这对于长期代码很重要—代码将持续一两年.

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.

假设一段代码大部分处于休眠状态(稳定)多年,但现在需要更改.您重新启用调试跟踪 - 但必须调试调试(跟踪)代码令人沮丧,因为它指的是在稳定维护的几年中已重命名或重新键入的变量.如果编译器(后预处理器)总是看到打印语句,它可以确保任何周围的更改都没有使诊断无效.如果编译器没有看到打印语句,它就不能保护你免受你自己的粗心(或你的同事或合作者的粗心).参见 Kernighan 和 Pike 的编程实践",尤其是第 8 章(另见维基百科TPOP).

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 中遇到了建议(请参阅我之前的评论),然后几年后确实启用了一些调试代码,并遇到了更改上下文破坏调试的问题.好几次,始终验证打印让我免于以后的问题.

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.

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

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!

/*
@(#)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 或更高版本的单参数变体

凯尔·布兰特问道:

Single argument variant for C99 or later

Kyle Brandt asked:

无论如何要这样做,即使没有参数,debug_print 仍然有效?例如:

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
", "Foo");

下面显示的 GCC-only 解决方案也提供了支持.

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

但是,您可以使用直接的 C99 系统使用:

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

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

与第一个版本相比,您失去了需要fmt"参数的有限检查,这意味着有人可以尝试不带参数调用debug_print()"(但参数列表中的尾随逗号为 fprintf() 将无法编译).检查丢失是否是一个问题是值得商榷的.

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.

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

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.

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

This technique is also supported by Clang for GCC compatibility.

这里 do while 的目的是什么?

What's the purpose of the do while here?

您希望能够使用宏,使其看起来像一个函数调用,这意味着它后面会跟一个分号.因此,您必须打包宏体以适应.如果您使用 if 语句而没有包围 do { ... } while (0),您将拥有:

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

现在,假设你写:

if (x > y)
    debug_print("x (%d) > y (%d)
", 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)
", 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)
", x, y);
    }
; // Null statement from semi-colon after macro
else
    do_something_useful(x, y);

else 现在是一个语法错误.do { ... } while(0) 循环避免了这两个问题.

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

这使得程序片段显示为有效.(void) 强制转换防止它在需要值的上下文中使用 —但它可以用作逗号运算符的左操作数,其中 do { ... } while (0) 版本不能.如果您认为您应该能够将调试代码嵌入到这样的表达式中,您可能更喜欢这个.如果您更喜欢要求调试打印作为完整的语句,那么 do { ... } while (0) 版本更好.请注意,如果宏的主体包含任何分号(粗略地说),那么您只能使用 do { ... } while(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/tpophttp://cm.bell-labs.com/cm/cs/tpop 但现在 (2015-08-10) 都坏了.

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.

如果你好奇,你可以在我的SOQ(堆栈溢出问题)存储库作为文件 debug.cdebug.hmddebug.csrc/libsoq子目录.

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 and mddebug.c in the src/libsoq sub-directory.

这篇关于#define 宏用于 C 中的调试打印?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆