web-dev-qa-db-ja.com

Cのデバッグ印刷用の#defineマクロ?

次の擬似コードのように、DEBUGが定義されている場合にデバッグメッセージの印刷に使用できるマクロを作成しようとしています。

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

これはマクロでどのように達成されますか?

192
jfarrell

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に固執していて、便利なコンパイラー拡張機能がない場合、C89を処理するための特にクリーンな方法はありません。私が使用したテクニックは次のとおりです。

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

そして、コードに次のように記述します。

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

二重括弧は非常に重要です。これが、マクロ展開に面白い表記がある理由です。前と同じように、コンパイラは常にコードの構文の妥当性をチェックします(これは良いことです)が、DEBUGマクロがゼロ以外と評価された場合にのみ、オプティマイザーは印刷機能を呼び出します。

これには、 'stderr'などを処理するためのサポート関数(例ではdbg_printf())が必要です。 varargs関数の書き方を知っている必要がありますが、それは難しくありません:

#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の両方の実装の背後にある1つの中心的なアイデアは、適切なコンパイラが常にデバッグprintfのようなステートメントを見ているということです。これは、長期的なコード、つまり10年または2年続くコードにとって重要です。

コードの一部が長年にわたってほとんど休止状態(安定)でしたが、今は変更する必要があるとします。デバッグトレースを再度有効にしますが、長年の安定したメンテナンス中に名前が変更または再入力された変数を参照するため、デバッグ(トレース)コードをデバッグする必要があります。コンパイラー(ポストプリプロセッサー)が常にprintステートメントを見る場合、周囲の変更が診断を無効にしないようにします。コンパイラーがprintステートメントを認識しない場合、ユーザー自身の不注意(または同僚や共同作業者の不注意)からユーザーを保護することはできません。 KernighanとPikeによる「 The Practice of Programming 」、特に第8章を参照してください(ウィキペディアの TPOP も参照)。

これは、「そこにいる、それをやった」経験です。私は本質的に、非デバッグビルドが長年(10年以上)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");

シンプルで古風なハックが1つあります。

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)

このソリューションは、フォーマット引数を必要とする利点を保持しながら、フォーマット後にオプションの引数を受け入れます。

この手法は、GCCとの互換性のために Clang でもサポートされています。


なぜdo-whileループなのですか?

ここのdo whileの目的は何ですか?

マクロを使用して、関数呼び出しのように見えるようにします。つまり、セミコロンが後に続きます。したがって、マクロ本体をパッケージ化する必要があります。周囲のdo { ... } while (0)なしでifステートメントを使用すると、次のようになります。

/* 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)ループはこれらの問題の両方を回避します。

動作する可能性のあるマクロを記述する別の方法が1つあります。

/* 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/tpop および http://cm.bell- labs.com/cm/cs/tpop が両方とも壊れた(2015-08-10)


GitHubのコード

興味があるなら、 src/libsoqdebug.cdebug.hおよびmddebug.cファイルとして、私の SOQ (Stack Overflow Questions)リポジトリのGitHubでこのコードを見ることができます。 =サブディレクトリ。

384

私はこのようなものを使用します:

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

接頭辞としてDを使用するよりも:

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

コンパイラはデバッグコードを認識し、コンマの問題はなく、どこでも動作します。また、printfが十分でない場合、たとえば、配列をダンプする必要がある場合や、プログラム自体に冗長な診断値を計算する必要がある場合にも機能します。

編集:[OK]を、それはこの注入されたelseによってインターセプトすることができるifが近くのどこかにあるときに問題を生成する可能性があります。これはそれを超えるバージョンです:

#ifdef DEBUG
 #define D 
#else
 #define D for(;0;)
#endif
28
mbq

ポータブル(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;
}
11
Marcin Koziuk

私が使用しているバージョンは次のとおりです。

#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
10
Christoph

私は次のようなことをします

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

これはきれいだと思います。

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

http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html によると、##の前に__VA_ARGS__が必要です。

それ以外の場合、マクロ#define dbg_print(format, ...) printf(format, __VA_ARGS__)は次の例をコンパイルしません:dbg_print("hello world");

6
Chobits Tai

私は何年もこれを行う方法を検討してきましたが、最終的に解決策を思いつきました。しかし、私はすでに他のソリューションがここにあることを知りませんでした。最初に、 Lefflerの答え との違いで、デバッグ出力は常にコンパイルされるべきであるという彼の議論は見当たりません。テストする必要があり、それらが最適化されていない可能性がある場合、必要でないときは、プロジェクトで不要なコードが実行されないようにします。

毎回コンパイルしないと、実際の実行時よりも悪く聞こえるかもしれません。ときどきコンパイルされないデバッグプリントを作成しますが、プロジェクトを完成させる前にコンパイルしてテストするのはそれほど難しくありません。このシステムでは、3レベルのデバッグを使用している場合、デバッグメッセージレベル3に設定し、コンパイルエラーを修正し、他のコードを確認してからyerコードを確定します。 (もちろん、デバッグステートメントのコンパイルは、意図したとおりに動作していることを保証するものではありません。)

私のソリューションは、デバッグの詳細レベルも提供します。そして、最高レベルに設定すると、すべてコンパイルされます。最近、高いデバッグ詳細レベルを使用している場合、それらはすべてその時点でコンパイルできました。最終更新は非常に簡単です。 3レベル以上は必要ありませんでしたが、ジョナサンは9レベルを使用したと言います。この方法(レフラーのような)は、任意の数のレベルに拡張できます。私の方法の使用法はもっと簡単かもしれません。コードで使用する場合、必要なステートメントは2つだけです。ただし、CLOSEマクロもコーディングしていますが、何もしませんが。私がファイルに送信していた場合、それは可能性があります。

コストに対して、配信前にコンパイルすることを確認するためにテストする追加のステップは、

  1. 最適化を行うにはそれらを信頼する必要があります。十分な最適化レベルがある場合は、間違いなくこれを行う必要があります。
  2. さらに、テスト目的で最適化をオフにしてリリースをコンパイルすると、おそらくそうなりません(確かにまれです)。デバッグ中はほとんど確実にまったく動作しません。これにより、実行時に数十または数百の「if(DEBUG)」ステートメントが実行されます。したがって、実行が遅くなり(これは私の原則的な反対です)、それほど重要ではありませんが、実行可能ファイルまたはdllのサイズが大きくなります。したがって、実行時間とコンパイル時間。ただし、Jonathanは、ステートメントをまったくコンパイルしないようにすることもできると教えてくれました。

実際、最新のプリフェッチプロセッサでは、ブランチのコストは比較的高くなります。あなたのアプリがタイムクリティカルなアプリでなければ、大した問題ではないでしょう。しかし、パフォーマンスが問題になる場合は、はい、かなり高速なデバッグコードを選択することをお勧めします。

だから、私が望んでいたのは、印刷しない場合はコンパイルしないが、印刷する場合はコンパイルするデバッグ印刷マクロです。また、デバッグレベルも必要だったので、たとえばコードのパフォーマンスに重要な部分を時々印刷せず、他の部分で印刷したい場合、デバッグレベルを設定し、追加のデバッグ印刷を開始することができます。デバッグレベルを実装する方法を見つけました。印刷物はコンパイルされたかどうかでした。私はこの方法でそれを達成しました:

DebugLog.h:

// FILE: DebugLog.h
// REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
// debug logging, currently; in addition to disabling it.  Level 3 is the most information.
// Levels 2 and 1 have progressively more.  Thus, you can write: 
//     DEBUGLOG_LOG(1, "a number=%d", 7);
// and it will be seen if DEBUG is anything other than undefined or zero.  If you write
//     DEBUGLOG_LOG(3, "another number=%d", 15);
// it will only be seen if DEBUG is 3.  When not being displayed, these routines compile
// to NOTHING.  I reject the argument that debug code needs to always be compiled so as to 
// keep it current.  I would rather have a leaner and faster app, and just not be lazy, and 
// maintain debugs as needed.  I don't know if this works with the C preprocessor or not, 
// but the rest of the code is fully C compliant also if it is.

#define DEBUG 1

#ifdef DEBUG
#define DEBUGLOG_INIT(filename) debuglog_init(filename)
#else
#define debuglog_init(...)
#endif

#ifdef DEBUG
#define DEBUGLOG_CLOSE debuglog_close
#else
#define debuglog_close(...)
#endif

#define DEBUGLOG_LOG(level, fmt, ...) DEBUGLOG_LOG ## level (fmt, ##__VA_ARGS__)

#if DEBUG == 0
#define DEBUGLOG_LOG0(...)
#endif

#if DEBUG >= 1
#define DEBUGLOG_LOG1(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG1(...)
#endif

#if DEBUG >= 2
#define DEBUGLOG_LOG2(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG2(...)
#endif

#if DEBUG == 3
#define DEBUGLOG_LOG3(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG3(...)
#endif

void debuglog_init(char *filename);
void debuglog_close(void);
void debuglog_log(char* format, ...);

DebugLog.cpp:

// FILE: DebugLog.h
// REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
// debug logging, currently; in addition to disabling it.  See DebugLog.h's remarks for more 
// info.

#include <stdio.h>
#include <stdarg.h>

#include "DebugLog.h"

FILE *hndl;
char *savedFilename;

void debuglog_init(char *filename)
{
    savedFilename = filename;
    hndl = fopen(savedFilename, "wt");
    fclose(hndl);
}

void debuglog_close(void)
{
    //fclose(hndl);
}

void debuglog_log(char* format, ...)
{
    hndl = fopen(savedFilename,"at");
    va_list argptr;
    va_start(argptr, format);
    vfprintf(hndl, format, argptr);
    va_end(argptr);
    fputc('\n',hndl);
    fclose(hndl);
}

マクロを使用する

使用するには、次のようにします。

DEBUGLOG_INIT("afile.log");

ログファイルに書き込むには、次のようにします。

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

閉じるには:

DEBUGLOG_CLOSE();

現在のところ、何もしないので、技術的に言えば、これは必要すらありません。ただし、動作方法について気が変わって、ロギングステートメント間でファイルを開いたままにしたい場合は、今でもCLOSEを使用しています。

次に、デバッグ印刷をオンにする場合は、ヘッダーファイルの最初の#defineを編集して、たとえば.

#define DEBUG 1

ロギングステートメントを何もコンパイルしないようにするには、

#define DEBUG 0

頻繁に実行されるコード(つまり、高レベルの詳細)から情報が必要な場合は、次のように記述できます。

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

DEBUGを3に定義すると、ログレベル1、2、および3がコンパイルされます。 2に設定すると、ログレベル1と2が得られます。1に設定すると、ログレベル1ステートメントのみが得られます。

Do-whileループについては、ifステートメントではなく、単一の関数または何も評価されないため、ループは不要です。 OK、C++ IOの代わりにCを使用するように私を非難します(Qtの場合、QtのQString :: arg()も変数をフォーマットするより安全な方法です。ドキュメントは整理されているとは限りませんが、必要な場合はまだありますが、.cppファイルに任意のコードを挿入できます。また、クラスの場合もありますが、インスタンス化してそれに追いつくか、new()を実行して保存する必要があります。このように、#include、init、およびオプションでcloseステートメントをソースにドロップするだけで、使用する準備が整います。しかし、あなたがそんなに傾いているなら、それは素晴らしいクラスになるでしょう。

以前は多くの解決策を見てきましたが、この基準と同様に私の基準に合うものはありませんでした。

  1. 好きなだけレベルを拡張することができます。
  2. 印刷しない場合は何にもコンパイルされません。
  3. IOを編集しやすい1つの場所に集中化します。
  4. Printfの書式設定を使用する柔軟性があります。
  5. 繰り返しますが、常にコンパイルされたデバッグ出力は常にデバッグモードで実行されますが、デバッグの実行は遅くなりません。コンピューターサイエンスを行っており、情報処理を書くのが簡単ではない場合、CPUを消費するシミュレーターを実行していることがあります。デバッガーは、ベクターの範囲外のインデックスでそれを停止します。これらは既にデバッグモードで非常に遅く実行されます。数百のデバッグプリントを強制的に実行すると、このような実行がさらに遅くなります。私にとって、このような実行は珍しいことではありません。

それほど重要ではありませんが、さらに:

  1. 引数なしで印刷するためのハックは必要ありません(例:DEBUGLOG_LOG(3, "got here!"););したがって、使用できるようにします。 Qtのより安全な.arg()フォーマット。 MSVCで動作するため、おそらくgccで動作します。 ##sで#defineを使用します。これは、Lefflerが指摘しているように非標準ですが、広くサポートされています。 (必要に応じて##を使用しないように再コーディングできますが、彼が提供するようなハックを使用する必要があります。)

警告:ログレベルの引数を指定するのを忘れると、MSVCは識別子が定義されていないことを助けにならないと主張します。

DEBUG以外のプリプロセッサシンボル名を使用することもできます。これは、一部のソースでもそのシンボルが定義されているためです(たとえば、./configureコマンドを使用してビルドの準備をするプログラム)。私がそれを開発したとき、それは私には自然に思えました。 DLLが他の何かによって使用されているアプリケーションで開発しました。ログプリントをファイルに送信するほうがより便利です。ただし、vprintf()に変更しても問題なく機能します。

これにより、デバッグロギングを行うための最良の方法を見つけ出すことについての多くの悲しみが軽減されることを願っています。またはあなたが好むかもしれないものをあなたに示します。私は何十年にもわたってこれを理解しようと中途半端でした。 MSVC 2012および2015で動作するため、おそらくgccで動作します。おそらく他の多くの作業も行っていますが、私はそれらをテストしていません。

私もこの1日のストリーミングバージョンを作成するつもりです。

注:StackOverflow向けにメッセージのフォーマットを改善してくれたLefflerに感謝します。

1
CodeLurker

以下の私のお気に入りは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)
1
Tom Hale

だから、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を使用しない限り、大丈夫だと思います。

確かに、私はそれ(そして文字列の関連バージョン、デバッグレベルなどのバージョン)が非常に貴重だと感じました。

これは私が使用するものです:

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

追加の引数がなくても、printfを適切に処理することには素晴らしい利点があります。 DBG == 0の場合、最も愚かなコンパイラーでさえも何も噛まないため、コードは生成されません。

1
5tenzel

テーマのこのバリエーションは、カテゴリごとに個別のマクロ名を持つ必要なく、デバッグカテゴリを提供すると思います。

プログラムスペースが32Kに制限され、ダイナミックメモリが2Kに制限されているArduinoプロジェクトでこのバリエーションを使用しました。デバッグステートメントとトレースデバッグ文字列を追加すると、すぐにスペースが消費されます。したがって、コンパイル時に含まれるデバッグトレースを、コードがビルドされるたびに必要最小限に制限できることが不可欠です。

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");
...
0
user358795