#Cでデバッグ印刷用のマクロを定義しますか?


209

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

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

これはマクロでどのように行われますか?


コンパイラー(gcc)はif(DEBUG){...}のようなステートメントを最適化しますか?実稼働コードでDEBUGマクロが0に設定されている場合は?デバッグステートメントをコンパイラに表示したままにしておくのには十分な理由があることは理解していますが、悪い感じが残っています。-Pat
Pat

回答:


410

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マクロがゼロ以外と評価された場合にのみ印刷機能を呼び出します。

これには、「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ステートメントを認識しない場合、自分自身の不注意(または同僚や共同作業者の不注意)からユーザーを保護することはできません。「を参照してくださいプログラミングの実践カーニハンとパイク、特に第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ここの目的は何ですか?

関数呼び出しのように見えるようにマクロを使用できるようにする必要があります。つまり、セミコロンが後に続きます。したがって、それに合わせてマクロ本体をパッケージ化する必要があります。を含まない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のコード

あなたのしている好奇心ならば、あなたは私の中でのGitHubでこのコードを見ることができますSOQファイルとして(スタックオーバーフローの質問)リポジトリdebug.cdebug.hおよびmddebug.cのsrc / libsoqの サブディレクトリ。


1
gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.htmlからのGCC ##-アプローチは、「単一引数のC99バリアント」という見出しの下で言及する価値があると思います。
Hugo Ideler 2012年

2
数年後、この答えは、printkをエイリアスする方法について、すべてのインターネットの中で依然として最も有用です!stdioが利用できないため、vfprintfはカーネル空間では機能しません。ありがとうございました! #define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
kevinf 2013

6
キーワードを使用した例では__FILE__, __LINE__, __func__, __VA_ARGS__、printfパラメータがない場合、つまり単に呼び出した場合はコンパイルされません 。## __ VA_ARGS__を使用すると、関数にパラメータを渡せないため、debug_print("Some msg\n"); これを修正できますfprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);
mc_electron 2014年

1
@LogicTom:違いが間にある#define debug_print(fmt, ...)#define debug_print(...)。これらの最初の引数には、少なくとも1つの引数、フォーマット文字列(fmt)、および0個以上の他の引数が必要です。2番目には、合計で0個以上の引数が必要です。前者とdebug_print()一緒に使用すると、マクロの誤用についてプリプロセッサからエラーが表示されますが、2番目ではエラーが発生しません。ただし、置換テキストが有効なCではないため、コンパイルエラーは引き続き発生します。したがって、実際にはそれほど大きな違いはありません。したがって、「制限されたチェック」という用語を使用します。
ジョナサンレフラー

1
上記のバリアント@ St.Antarioは、アプリケーション全体で単一のアクティブなデバッグレベルを使用します。通常、コマンドラインオプションを使用して、プログラムの実行時にデバッグレベルを設定できるようにします。複数の異なるサブシステムを認識するバリアントもあり、それぞれに名前と独自のデバッグレベルが与えられているため-D input=4,macros=9,rules=2、入力システムのデバッグレベルを4に設定し、マクロシステムを9に設定できます(詳細な調査が行われています) )とルールシステムを2に。テーマには無限のバリエーションがあります。あなたに合ったものを使用してください。
ジョナサンレフラー

28

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

#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

3
についてはfor(;0;)D continue;またはのようなものを書くと問題が発生する可能性がありますD break;
ACcreator 2014

1
私を得た; しかし、偶然に起こる可能性は非常に低いようです。
mbq 2015

11

移植可能な(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;
}

3
@LB:プリプロセッサを「考える」には、引数が1つしかないようにしますが、_は後の段階で展開します。
Marcin Koziuk、2009年

10

ここに私が使用するバージョンがあります:

#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

9

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

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

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


フラグとしてテスト内でマクロを使用するという考えはあまり好きではありません。デバッグ出力を常にチェックする必要がある理由を説明していただけますか?
LB40 2009年

1
@ジョナサン:コードがデバッグモードでのみ実行される場合、なぜそれが非デバッグモードでコンパイルされるかどうか気にする必要がありますか?assert()stdlibからも同じように機能し、通常NDEBUGは自分のデバッグコードにマクロを再利用します...
Christoph

テストでDEBUGを使用すると、誰かが制御されていないundef DEBUGを実行すると、コードはコンパイルされなくなります。正しい ?
LB40 2009年

4
デバッグを有効にしてからデバッグコードをデバッグする必要があるのは、名前が変更されたか再入力された変数などを参照するため、イライラします。コンパイラー(ポストプリプロセッサー)が常に印刷ステートメントを確認する場合、周囲の変更が確実に行われます。診断を無効にしていない。コンパイラーがprintステートメントを認識しない場合、自分自身の不注意(または同僚や共同作業者の不注意)からユーザーを保護することはできません。KernighanとPikeによる「The Practice of Programming」を参照してください-plan9.bell-labs.com/cm/cs/tpop
ジョナサンレフラー、

1
@Christoph:まあ、そうですね... NDEBUGを使用してアサーションのみを制御し、別のマクロ(通常はDEBUG)を使用してデバッグトレースを制御します。デバッグ出力が無条件に表示されることを望まないことが多いので、出力が表示されるかどうかを制御するメカニズムがあります(デバッグレベル、およびfprintf()を直接呼び出す代わりに、条件付きでのみ印刷するデバッグ印刷関数を呼び出します。コードは、プログラムオプションに基づいて印刷することも印刷しないこともできます。すべてのビルドで、コンパイラーは診断ステートメントを表示する必要があることを私は主張しています。ただし、デバッグが有効になっていない限り、コードは生成されません。
ジョナサンレフラー

8

http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.htmlによると、##前にあるはず__VA_ARGS__です。

そうし#define dbg_print(format, ...) printf(format, __VA_ARGS__)ないと、マクロは次の例をコンパイルしませんdbg_print("hello world");


1
Stack Overflowへようこそ。GCCがあなたが参照する非標準の拡張子を持っていることは正しいです。現在受け入れられている回答は、実際にあなたが与える参照URLを含めて、これについて実際に言及しています。
ジョナサンレフラー、2012年

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

Cのどのバージョンがその表記法をサポートしていますか?そして、それが機能した場合、そのようなすべての引数を貼り付けたトークンは、フォーマット文字列のオプションのセットが非常に限られていることを意味しますか?
ジョナサンレフラー、

@ジョナサン:gcc(Debian 4.3.3-13)4.3.3
eyalm 2009年

1
OK-同意:古いGNU拡張として文書化されています(GCC 4.4.1マニュアルのセクション5.17)。しかし、おそらくそれがGCCでのみ機能することを文書化する必要があります。
ジョナサンレフラー

1
私の意図は、引数を使用する別のスタイルを示し、主にFUNCTIONおよびLINEの
eyalm

2

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

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

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


コンパイラに常にデバッグコードをチェックさせることをお勧めします。
Jonathan Leffler、2015

1

以下の私のお気に入りはです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

だから、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

また、どの式が何に対して評価されたかを分析できます。

二重評価の問題から保護されていますが、gensymがないため、名前の衝突が発生しやすくなっています。

ただし、ネストします。

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

確かに、私はそれ(および文字列の関連バージョン、デバッグレベルのバージョンなど)が非常に貴重であることを発見しました。


1

私はこれを何年も行う方法を煮詰めてきましたが、最終的に解決策を考え出しました。しかし、私はすでに他の解決策がここにあることを知りませんでした。まず、Lefflerの回答との違いは、デバッグ出力を常にコンパイルする必要があるという彼の主張がわかりません。私がテストする必要があり、それらが最適化されていない可能性がある場合、不要なときに、プロジェクトで大量の不要なコードを実行したくありません。

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

私のソリューションでは、デバッグの詳細レベルも提供しています。最高レベルに設定すると、すべてコンパイルされます。最近高いデバッグ詳細レベルを使用している場合、それらはすべてその時点でコンパイルできました。最終更新は非常に簡単です。私は3つ以上のレベルは必要としませんでしたが、ジョナサンは彼が9つを使用したと言います。この方法(レフラーの方法など)は、任意の数のレベルに拡張できます。私の方法の使い方はもっと簡単かもしれません。コードで使用する場合、必要なステートメントは2つだけです。ただし、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

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

 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のQString :: arg()もQtで変数をフォーマットする安全な方法です—かなり洗練されていますが、より多くのコードを必要とし、フォーマットのドキュメントは整理されていませんそれはそうかもしれません-しかし、それでも私はそれが望ましいケースを見つけました)、しかしあなたはあなたが望む.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、ビルドを準備するコマンドを使用するprogsなど)。それを開発したとき、私には自然に思えました。DLLが別のアプリケーションで使用されているアプリケーションで開発しましたが、ログプリントをファイルに送信するのがより一般的です。しかし、それをvprintf()に変更してもうまくいきます。

これにより、デバッグロギングを行うための最良の方法を考え出すことについて多くの人が悲しみを解消できることを願っています。またはあなたが好むかもしれないものを示しています。私は中途半端にこれを何十年も理解しようと努力してきました。MSVC 2012および2015で動作するため、おそらくgccで動作します。おそらく他の多くのプロジェクトにも取り組んでいますが、それらについてはテストしていません。

私もこの日のストリーミング版を作るつもりです。

注:Lefflerに感謝します。Lefflerは、StackOverflowに合わせてメッセージをより適切にフォーマットするために協力してくれました。


2
「実行時に数十または数百のif (DEBUG)ステートメントを実行すると、最適化されない」と言います。これは、風車傾いています。私が説明したシステムの重要な点は、コードがコンパイラーによってチェックされることです(重要で自動-特別なビルドは不要)。ただし、デバッグコード最適化されているため、まったく生成されません(実行時の影響はゼロです)。実行時にコードが存在しないため、コードサイズまたはパフォーマンス。
ジョナサンレフラー

ジョナサン・レフラー:私の言い間違いを指摘してくれてありがとう。私は自分の考えを私の指よりも速くレースさせ、これが終わってとても嬉しかったです。「... 1)最適化を行うには信頼する必要があります。十分な最適化レベルがある場合、これは確かに発生します。2)さらに、最適化を使用してリリースをコンパイルした場合、そうではありません。テスト目的でオフにした場合、おそらくデバッグ中にはまったく機能しません。そのため、実行時に数十または数百の「if(DEBUG)」ステートメントが実行され、実行可能ファイルまたはdllのサイズと実行時間が増加します。」
CodeLurker

あなたが私がやっている他の重要なことをするためには、デバッグレベルが必要です。多くの場合、それらをオンにする必要はありませんが、いくつかのアプリケーションは、単純な "#define DEBUG 3"でタイムクリティカルなループに関する詳細なレベルを取得し、次に戻ることができるというメリットがあります。 "#define DEBUG 1"を使用すると、情報が大幅に少なくなります。私は3レベルを超えるレベルを必要としなかったため、少なくともリリースの約1/3のデバッグがすでにコンパイルされています。私が最近レベル3を使用した場合、おそらくすべてが使用します。
CodeLurker

YMMV。私が示した最新のシステムは、デバッグレベルの動的(実行時)設定をサポートしているため、実行時に生成されるデバッグの量をプログラムで決定できます。私は通常、レベル1〜9を使用しましたが、上限はありません(または下限はありません。デフォルトレベルは0で、通常はオフですが、アクティブな開発中に必要に応じて明示的に要求できます。長期的な作業には適していません)。私はデフォルトのレベル3を選択しました。物事を調整することができます。これは私に多くのコントロールを与えます。非アクティブなときにデバッグコードをテストしたくない場合は、代替をに変更します((void)0)—簡単です。
Jonathan Leffler

1
ああ。それはすべてを読むのに役立ちました。かなり長いポストです。今のところそれは本質的なポイントを持っていると思います。あなたのものは、私のように、すべてのデバッグ出力をコンパイルするかどうかに使用でき、レベルをサポートできることがわかりました。確かに、あなたはあなたが使っていないレベルをコンパイルすることができます-デバッグ中の代償。
CodeLurker

0

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

プログラムスペースが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");
...
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.