簡體   English   中英

#define 用於在 C 中調試打印的宏?

[英]#define macro for debug printing in C?

在定義 DEBUG 時嘗試創建一個可用於打印調試消息的宏,如以下偽代碼:

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

這是如何用宏完成的?

如果您使用 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__技術更簡潔,因為它使用常規函數表示法,而不是雙括號 hack。

為什么編譯器總是能看到調試代碼很重要?

[重新散列對另一個答案的評論。 ]

上述 C99 和 C89 實現背后的一個中心思想是,適當的編譯器始終可以看到調試 printf-like 語句。 這對於長期代碼很重要——將持續一兩年的代碼。

假設一段代碼大部分時間都處於休眠狀態(穩定)多年,但現在需要更改。 您重新啟用調試跟蹤 - 但不得不調試調試(跟蹤)代碼令人沮喪,因為它指的是在穩定維護期間已重命名或重新鍵入的變量。 如果編譯器(后預處理器)總是看到 print 語句,它確保任何周圍的變化都沒有使診斷無效。 如果編譯器沒有看到 print 語句,它就不能保護您免受您自己的粗心(或您的同事或合作者的粗心)的影響。 請參閱 Kernighan 和 Pike的“編程實踐”,尤其是第 8 章(另請參閱關於TPOP的 Wikipedia)。

這是“去過那里,做過那個”的經驗——我基本上使用了其他答案中描述的技術,其中非調試版本在多年(十多年)內看不到類似 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 或更高版本的單參數變體

凱爾勃蘭特問:

無論如何要這樣做,即使沒有參數, debug_print仍然有效? 例如:

 debug_print("Foo");

有一個簡單的老式 hack:

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 兼容性。


為什么是 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);

不幸的是,該縮進並不能反映對流的實際控制,因為預處理器會生成與此等效的代碼(縮進和大括號是為了強調實際含義):

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 以前位於http://plan9.bell-labs.com/cm/cs/tpophttp://cm.bell-labs.com/cm/cs/tpop但現在都在 (2015-08-10)破碎的。


GitHub 中的代碼

如果您好奇,可以在我的SOQ (堆棧溢出問題)存儲庫中查看 GitHub 中的此代碼,作為src/libsoq子目錄中的文件debug.cdebug.hmddebug.c

我使用這樣的東西:

#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

對於可移植(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;
}

或(hackish,不推薦)

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

這是我使用的版本:

#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

我會做類似的事情

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

我認為這更清潔。

根據http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html ,在__VA_ARGS__之前應該有一個##

否則,宏#define dbg_print(format, ...) printf(format, __VA_ARGS__)將無法編譯以下示例: dbg_print("hello world"); .

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

這就是我使用的:

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

正確處理 printf 有很大的好處,即使沒有額外的參數。 在 DBG ==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

並且您可以分析什么表達式被評估為什么。

它可以防止雙重評估問題,但是缺少 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 作為變量名,就可以了。

當然,我發現它(以及字符串的相關版本,以及調試級別的版本等)非常寶貴。

多年來我一直在苦苦思考如何做到這一點,最后想出了一個解決方案。 但是,我不知道這里已經有其他解決方案。 首先,與Leffler 的回答不同,我沒有看到他認為應該始終編譯調試打印的論點。 我寧願不要在我的項目中執行大量不需要的代碼,當不需要時,在我需要測試並且它們可能沒有得到優化的情況下。

不是每次都編譯聽起來可能比實際情況更糟。 您確實會得到有時無法編譯的調試打印,但在完成項目之前編譯和測試它們並不難。 使用此系統,如果您使用三個級別的調試,只需將其置於調試消息級別 3,修復您的編譯錯誤並檢查任何其他錯誤,然后再完成您的代碼。 (當然,調試語句編譯並不能保證它們仍然按預期工作。)

我的解決方案還提供了調試細節級別; 如果您將其設置為最高級別,它們都會編譯。 如果您最近一直在使用高調試詳細級別,那么它們當時都能夠編譯。 最終更新應該很容易。 我從來不需要超過三個級別,但喬納森說他用了九個。 這種方法(如 Leffler 的)可以擴展到任意數量的級別。 我的方法的使用可能會更簡單一些; 在您的代碼中使用時只需要兩個語句。 然而,我也在編寫 CLOSE 宏——盡管它什么也沒做。 如果我要發送到文件,它可能會。

與成本相比,測試它們以查看它們是否會在交付前編譯的額外步驟是

  1. 你必須相信他們會得到優化,如果你有足夠的優化級別,這當然應該發生。
  2. 此外,如果您出於測試目的在關閉優化的情況下進行發布編譯(這確實很少見),它們可能不會; 而且它們幾乎可以肯定在調試期間根本不會——從而在運行時執行數十或數百個“if (DEBUG)”語句; 從而減慢執行速度(這是我的主要反對意見),不太重要的是,增加了可執行文件或 dll 的大小; 因此執行和編譯時間。 然而,喬納森告訴我,他的方法也可以完全不編譯語句。

在現代預取處理器中,分支實際上是相當昂貴的。 如果您的應用程序不是時間關鍵型應用程序,可能沒什么大不了的; 但如果性能是一個問題,那么,是的,這是一個足夠大的交易,我寧願選擇執行速度更快的調試代碼(如前所述,在極少數情況下可能會更快地發布)。

所以,我想要的是一個調試打印宏,如果它不被打印,它就不會編譯,但如果它是的話。 我還想要調試級別,例如,如果我希望代碼的性能關鍵部分在某些時候不打印,而是在其他時候打印,我可以設置調試級別,並進行額外的調試打印。我遇到了一種實現調試級別的方法,該級別確定打印是否已編譯。 我是這樣實現的:

調試日志.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, ...);

調試日志.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 語句。

至於 do-while 循環,由於 this 計算結果為單個函數或什么都沒有,而不是 if 語句,因此不需要循環。 好的,譴責我使用 C 而不是 C++ IO(並且 Qt 的 QString::arg() 也是在 Qt 中格式化變量的一種更安全的方法——它非常漂亮,但是需要更多的代碼並且格式化文檔沒有那么有條理可能是這樣 - 但我仍然發現了它更可取的情況),但您可以將任何代碼放入您想要的 .cpp 文件中。 它也可能是一個類,但是您需要實例化它並跟上它,或者執行 new() 並存儲它。 這樣,您只需將#include、init 和可選的關閉語句放入源代碼中,就可以開始使用它了。 然而,如果你願意的話,它會成為一堂很好的課。

我以前見過很多解決方案,但沒有一個比這個更適合我的標准。

  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 和 2015 中工作,因此可能在 gcc 上工作; 以及可能在許多其他人身上工作,但我還沒有在他們身上測試過。

我的意思是有一天也制作一個流媒體版本。

注意:感謝 Leffler,他熱心幫助我更好地為 StackOverflow 格式化我的消息。

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

我相信這個主題的變體提供了調試類別,而不需要每個類別有一個單獨的宏名稱。

我在一個 Arduino 項目中使用了這種變體,其中程序空間限制為 32K,動態內存限制為 2K。 添加調試語句和跟蹤調試字符串會很快占用空間。 因此,必須能夠將編譯時包含的調試跟蹤限制在每次構建代碼時所需的最低限度。

調試.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");
...

如果你不關心輸出到標准輸出,你可以使用這個:

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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM