ç的#define宏调试印刷 [英] C #define macro for debug printing

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

问题描述

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

 将#define DEBUG 1
#定义debug_print(参数...)如果(调试)fprintf中(标准错误,参数)

这是

如何完成与宏?


解决方案

如果你使用C99编译器

 的#define debug_print(FMT,...)\\
            做{如果(调试)fprintf中(标准错误,格式化,__VA_ARGS__); }而(0)

假设你正在使用C99(可变参数列表符号是不是在早期版本支持)。在做{...}而(0)成语确保code的作用就像一个声明(函数调用)。无条件的使用code可确保编译器总是会检查调试code是有效的—但优化器将删除code当DEBUG为0。

如果你想用的#ifdef DEBUG工作,然后改变测试条件:

  #IFDEF DEBUG
#定义DEBUG_TEST 1
#其他
#定义DEBUG_TEST 0
#万一

然后用DEBUG_TEST,我用DEBUG。

如果你坚持一个字符串格式字符串(可能反正是个好主意),你还可以引入的东西像 __ FILE __ __ LINE__ __ __ FUNC 到输出,从而可以提高诊断:

 的#define debug_print(FMT,...)\\
        做{如果(调试)fprintf中(标准错误,%S:%D:%S():FMT,__FILE__,\\
                                __LINE__,__func__,__VA_ARGS__); }而(0)

这依赖于字符串连接比程序员编写创建一个更大的格式字符串。

如果你使用C89编译器

如果你被卡住C89和没有有用的编译器扩展,那么就没有一个特别清晰的方式来处理它。我以前用的技术是:

 的#define TRACE(X)做{如果(调试)dbg_printf X; }而(0)

然后,在code,写的:

  TRACE((信息%d个\\ N,VAR));

双括号是至关重要的—并且为什么你在宏展开有趣的符号。和以前一样,编译器总是检查语法有效性code(这是好的),但只有优化如果调试宏解释为非零调用打印功能。

这确实需要一个支持函数— dbg_printf()中的实施例—处理之类的东西标准错误。它要求你知道如何写可变参数的功能,但是这并不难:

 的#include<&STDARG.H GT;
#包括LT&;&stdio.h中GT;无效dbg_printf(为const char * FMT,...)
{
    va_list的ARGS;
    的va_start(参数,FMT);
    vfprintf(标准错误,格式化,参数);
    va_end用来(参数);
}

您也可以使用C99这种技术,当然,但 __ VA_ARGS __ 技术是整洁的,因为它使用普通函数符号,而不是双括号破解。

为什么至关重要的编译器总能看到调试code?

[老调重弹到另一个答案提出的意见。的]

C99的C89和实现都背后的一个中心思想上面是编译器适当总是能看到调试类printf语句。这对于长期code&MDASH重要; code,将持续十年或二十年。

假设一块$ C $的c已大多休眠(稳定)为了数年,但现在需要改变。您重新启用调试跟踪 - 但它是令人沮丧不得不调试调试(跟踪)code,因为它是指在多年的稳定维持已重命名或重新输入,变量。如果编译器(后pre-处理器)的总是能看到打印语句,它确保了周围的任何变化都没有失效诊断。如果编译器不看到打印语句,它不能保护你对自己的粗心(或您的同事或合作者的疏忽)。参见编程的实践由Kernighan和派克,尤其是第8章(见维基百科 TPOP )。

这是在那里,这样做的经验和MDASH;我基本上是用来在其他的答案中描述的技术,其中非调试版本并没有看到类似printf语句数年(十年以上)。但是,我在对面的TPOP来到意见(见我的previous评论),然后做了使若干年后一些调试code,跑进打破了调试变化的情况下的问题。有好几次,总是有验证印刷已经从后面的问题救了我。

我使用NDEBUG只控制的断言,和一个单独的宏(通常DEBUG)来控制调试跟踪是否被内置到该程序。即使在调试跟踪是内置的,我经常不想调试输出无条件地出现,所以我有机制来控制是否显示输出(调试水平,而不是调用fprintf中()直接,我称之为调试打印函数只有有条件地打印所以code的相同版本可以打印或不打印基于程序选项)。我也有code更大节目的多子系统的版本,这样我就可以有计划生产不同量的微量的不同部分 - 运行控制下

我主张所有构建,编译器应该看到诊断报告;然而,除非调试启用编译器不会产生任何code的调试跟踪语句。基本上,这意味着你的所有code是由编译器每次编译时检查 - 无论是为了释放或调试。这是个好东西!

debug.h - 1.2版(1990年5月1日)

  / *
@(#)文件:$ RCSfile:debug.h,V $
@(#)版本:$修订:$ 1.2
@(#)上次修改:$日期:1990年5月1日12时55分39秒$
@(#)用途:定义为系统调试
@(#)作者:J-莱弗勒
* /的#ifndef DEBUG_H
#定义DEBUG_H/ * - 宏定义* /#IFDEF DEBUG
将#define TRACE(X)db_print点¯x
#其他
将#define TRACE(X)
#ENDIF / * * DEBUG // * - 声明* /#IFDEF DEBUG
EXTERN INT调试;
#万一#ENDIF / * * DEBUG_H /

debug.h - 3.6版(2008-02-11)

  / *
@(#)文件:$ RCSfile:debug.h,V $
@(#)版本:$修订:$ 3.6
@(#)上次修改:$日期:2008年2月11日6时46分37秒$
@(#)用途:定义为系统调试
@(#)作者:J-莱弗勒
@(#)版权所有:(C)JLSS 1990-93,1997-99,2003,2005,2008
@(#)产品:产品:
* /的#ifndef DEBUG_H
#定义DEBUG_H下列块#ifdef HAVE_CONFIG_H
的#includeconfig.h中
#ENDIF / * * HAVE_CONFIG_H // *
**用法:TRACE((水平,FMT,...))
**级别是必须是可操作用于输出的调试级别
** 出现。 FMT是一个格式化字符串。 ...是什么额外的
**参数FMT要求(可能没有)。
**非调试宏意味着code被验证,但从来没有被调用。
** - 的编程实践见第8章,由Kernighan和派克。
* /
#IFDEF DEBUG
将#define TRACE(X)db_print点¯x
#其他
将#define TRACE(X)做{如果(0)db_print X; }而(0)
#ENDIF / * * DEBUG /皮棉的#ifndef
#IFDEF DEBUG
/ *此字符串无法进行的extern - 一般*多重定义/
静态为const char jlss_id_debug_enabled [] =@(#)*** *** DEBUG
#ENDIF / * * DEBUG /
#IFDEF MAIN_PROGRAM
为const char jlss_id_debug_h [] =@(#)$编号:debug.h,V 3.6 2008年2月11日6时46分37秒jleffler精通$;
#ENDIF / * * MAIN_PROGRAM /
#ENDIF / *皮棉* /#包括LT&;&stdio.h中GT;EXTERN INT db_getdebug(无效);
EXTERN INT db_newindent(无效);
EXTERN INT db_oldindent(无效);
EXTERN INT db_setdebug(INT级);
EXTERN INT db_setindent(int i)以;
EXTERN无效db_print(INT级,为const char * FMT,...);
EXTERN无效db_setfilename(为const char * FN);
EXTERN无效db_setfileptr(FILE * FP);
的extern FILE * db_getfileptr(无效);/ *半私有函数* /
EXTERN为const char * db_indent(无效);/ *********** \\
**多种调试SUBSYSTEMS code **
\\ *********** // *
**用法:MDTRACE((SUBSYS,水平,FMT,...))
**SUBSYS就是这本声明所属的调试系统。
**子系统的意义是由程序员确定
**除了诸如功能db_print参阅子系统0。
**级别是必须运行的调试级别
**输出出现。 FMT是一个格式化字符串。 ...是
**任何额外的参数FMT要求(可能没有)。
**非调试宏意味着code被验证,但从来没有被调用。
* /
#IFDEF DEBUG
#定义MDTRACE(X)db_mdprint点¯x
#其他
#定义MDTRACE(X)做{如果(0)db_mdprint X; }而(0)
#ENDIF / * * DEBUG /EXTERN INT db_mdgetdebug(INT SUBSYS);
EXTERN INT db_mdparsearg(字符* ARG);
EXTERN INT db_mdsetdebug(INT SUBSYS,INT级);
EXTERN无效db_mdprint(INT SUBSYS,INT级,为const char * FMT,...);
EXTERN无效db_mdsubsysnames(字符常量* const的*名称);#ENDIF / * * DEBUG_H /

单参数C99变种

凯尔·勃兰特问:


  

总之要做到这一点,以便 debug_print 仍然有效,即使没有参数?例如:

  debug_print(富);


还有一个简单的老式黑客:

  debug_print(%S \\ n,富);

在GCC-唯一的解决方案也提供了支持为。

不过,您可以通过使用直C99系统做到这一点:

 的#define debug_print(...)\\
            做{如果(调试)fprintf中(标准错误,__VA_ARGS__); }而(0)

相比于第一个版本,你失去它要求格式化的说法,这意味着有人可以称之为'debug_print()不带参数的限量检查。无论是检查的损失是在所有的一个问题是值得商榷的。

GCC特有的技术

有些编译器提供的宏处理可变长度参数列表等方式扩展。具体而言,在由雨果Ideler ,GCC,您可以省略通常会出现后面的逗号最后一个固定的说法宏。它还允许您使用 ## __ __ VA_ARGS 中的宏替换文本,它会删除逗号preceding的符号,如果,但仅当previous令牌是一个逗号:

 的#define debug_print(...)\\
            做{如果(调试)fprintf中(标准错误,## __ VA_ARGS__); }而(0)

该解决方案保留了第一个版本的好处。


为什么do-whil​​e循环?


  

有什么目的的做的,而在这里?


您希望能够使用宏,所以它看起来像一个函数调用,这意味着它会跟着一个分号。因此,你必须打包宏体,以适应。如果使用如果语句周围无做{...}而(0),你将有:

  / * BAD  - 坏 -  BAD * /
#定义debug_print(...)\\
            如果(调试)fprintf中(标准错误,__VA_ARGS__)

现在,假设你写的:

 如果(X> Y)
    debug_print(X(%D)> Y(%D)\\ n,X,Y);
其他
    do_something_useful(X,Y);

不幸的是,压痕并不反映流量的实际控制人,因为preprocessor产生code相当于本(缩进和大括号加强调实际意义):

 如果(X> Y)
{
    如果(调试)
        fprintf中(标准错误,×(%D)> Y(%D)\\ n,X,Y);
    其他
        do_something_useful(X,Y);
}

在宏观下次尝试可能是:

  / * BAD  - 坏 -  BAD * /
#定义debug_print(...)\\
            如果(调试){fprintf中(标准错误,__VA_ARGS__); }

和同一code片段现在生产:

 如果(X> Y)
    如果(调试)
    {
        fprintf中(标准错误,×(%D)> Y(%D)\\ n,X,Y);
    }
;从宏观后分号//空语句
其他
    do_something_useful(X,Y);

以及其他现在是一个语法错误。在做{...}而(0)循环避免了这两个问题。

有写作这可能工作的宏另一种方式:

  / * BAD  - 坏 -  BAD * /
#定义debug_print(...)\\
            ((无效)((调试)fprintf中(标准错误,__VA_ARGS__):0))

这使得显示为有效的程序片段。在(无效)它的上下文中使用铸铁prevents在需要与MDASH的值;但它可以作为一个逗号操作符的左操作数,其中做{...}而(0)版本不能。如果你认为你应该能够嵌入调试code到这样的前pressions,你可能preFER这一点。如果preFER,要求调试打印作为一个完整的语句,那么做{...}而(0)版本更好。请注意,如果宏体参与任何分号(粗略地讲),那么你只能使用做{...}而(0)符号。它总是工作;恩pression语句机制可以更加难以应用。您还可能得到与前pression形式,你会preFER避免编译器警告;这将取决于编译器和使用标志。



TPOP是previously在
http://plan9.bell-labs.com/cm/cs/ TPOP http://cm.bell-labs.com/cm/cs/tpop但两者现在(2015年8月10日)打破了。

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?

解决方案

If you use a C99 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 $";
#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 C99 variant

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 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 call 'debug_print()' with no arguments. Whether the loss of checking is a problem at all is debatable.

GCC-specific Technique

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(...) \
            do { if (DEBUG) fprintf(stderr, ##__VA_ARGS__); } while (0)

This solution retains the benefits of first version.


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.

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

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