75

私が本当に好きなCのDEBUGマクロに遭遇しました

#ifdef DEBUG_BUILD
#  define DEBUG(x) fprintf(stderr, x)
#else
#  define DEBUG(x) do {} while (0)
#endif

C ++アナログは次のようになると思います:-

#ifdef DEBUG_BUILD
#  define DEBUG(x) cerr << x
#else
#  define DEBUG(x) do {} while (0)
#endif
  1. 2番目のコードスニペットはCのコードスニペットに類似していますか?
  2. お気に入りのC++デバッグマクロはありますか?

編集:「デバッグマクロ」とは、「デバッグモードでプログラムを実行しているときに役立つ可能性のあるマクロ」を意味します。

4

10 に答える 10

51

2番目のコードスニペットはCのコードスニペットに類似していますか?

多かれ少なかれ。引数に-separated値を含めることができるため、より強力です<<。したがって、単一の引数を使用すると、Cで可変数のマクロ引数を必要とするものが得られます。一方、人々が悪用する可能性はわずかです。引数にセミコロンを含めることによって。または、呼び出し後にセミコロンを忘れたためにミスが発生することもあります。だから私はこれをdoブロックに含めます:

#define DEBUG(x) do { std::cerr << x; } while (0)

お気に入りのC++デバッグマクロはありますか?

私は上記のものが好きで、それをかなり頻繁に使用します。私のno-opは通常ただ読む

#define DEBUG(x)

これは、コンパイラーの最適化にも同じ効果があります。以下の@TonyDによるコメントは正しいですが、これにより、一部の構文エラーが検出されないままになる可能性があります。

実行時チェックも含めることがあるので、何らかの形のデバッグフラグを提供します。@Tony Dが私に思い出させたように、そこにendlがあることもしばしば役に立ちます。

#define DEBUG(x) do { \
  if (debugging_enabled) { std::cerr << x << std::endl; } \
} while (0)

式を印刷したい場合もあります。

#define DEBUG2(x) do { std::cerr << #x << ": " << x << std::endl; } while (0)

__FILE__一部のマクロでは、、__LINE__またはを含めるのが好きです__func__が、これらはより多くの場合アサーションであり、単純なデバッグマクロではありません。

于 2013-01-10T10:56:47.287 に答える
40

これが私のお気に入りです

#ifdef DEBUG 
#define D(x) x
#else 
#define D(x)
#endif

これは非常に便利で、きれいな (そして重要なことに、リリース モードでは高速です!!) コードを作成できます。

(コードのデバッグ関連のブロックを除外するために) いたるところにたくさんの#ifdef DEBUG_BUILDブロックがあるのはかなり醜いですが、数行を . で囲むとそれほど悪くはありませんD()

使い方:

D(cerr << "oopsie";)

それがまだあなたにとって醜い/奇妙/長すぎる場合は、

#ifdef DEBUG
#define DEBUG_STDERR(x) (std::cerr << (x))
#define DEBUG_STDOUT(x) (std::cout << (x))
//... etc
#else 
#define DEBUG_STDERR(x)
#define DEBUG_STDOUT(x)
//... etc
#endif

(使用しないことをお勧めしますが、良いアイデアusing namespace std;かもしれません)using std::cout; using std::cerr;

「デバッグ」について考えているときは、stderr に出力するだけでなく、もっと多くのことをしたいかもしれないことに注意してください。創造力を働かせれば、プログラム内の最も複雑な相互作用への洞察を提供する構成を構築でき、同時に、デバッグ インストルメンテーションによって妨げられない非常に効率的なバージョンの構築に非常に迅速に切り替えることができます。

たとえば、私の最近のプロジェクトの 1 つで、データ構造内の大きなツリーを視覚化するために、Graphviz 互換のグラフをドット形式でダンプFILE* file = fopen("debug_graph.dot");することから始まる巨大なデバッグ専用ブロックがありました。さらに優れているのは、OS X のグラフビズ クライアントは、ファイルが変更されたときにディスクからファイルを自動的に読み取るため、プログラムが実行されるたびにグラフが更新されることです。

また、デバッグ専用のメンバーと関数を使用してクラス/構造体を「拡張」することも特に好きです。これにより、バグの追跡に役立つ機能と状態を実装する可能性が開かれ、デバッグ マクロにラップされている他のすべてのものと同様に、ビルド パラメーターを切り替えることで削除されます。状態が更新されるたびに各コーナー ケースを念入りにチェックする巨大なルーチンですか? 問題ない。その周りを平手打ちしD()ます。動作することを確認したら-DDEBUG、ビルド スクリプトから削除します。つまり、リリース用にビルドします。これで、ユニット テストなどのためにすぐに再有効化できるようになります。

この概念の (おそらくやや熱心すぎる) 使用法を説明するための、やや完全な大規模な例:

#ifdef DEBUG
#  define D(x) x
#else
#  define D(x)
#endif // DEBUG

#ifdef UNITTEST
#  include <UnitTest++/UnitTest++.h>
#  define U(x) x // same concept as D(x) macro.
#  define N(x)
#else
#  define U(x)
#  define N(x) x // N(x) macro performs the opposite of U(x)
#endif

struct Component; // fwd decls
typedef std::list<Component> compList;

// represents a node in the graph. Components group GNs
// into manageable chunks (which turn into matrices which is why we want
// graph component partitioning: to minimize matrix size)
struct GraphNode {
    U(Component* comp;) // this guy only exists in unit test build
    std::vector<int> adj; // neighbor list: These are indices
    // into the node_list buffer (used to be GN*)
    uint64_t h_i; // heap index value
    U(int helper;) // dangling variable for search algo to use (comp node idx)
    // todo: use a more space-efficient neighbor container?
    U(GraphNode(uint64_t i, Component* c, int first_edge):)
    N(GraphNode(uint64_t i, int first_edge):)
        h_i(i) {
        U(comp = c;)
        U(helper = -1;)
        adj.push_back(first_edge);
    }
    U(GraphNode(uint64_t i, Component* c):)
    N(GraphNode(uint64_t i):)
        h_i(i)
    {
        U(comp=c;)
        U(helper=-1;)
    }
    inline void add(int n) {
        adj.push_back(n);
    }
};

// A component is a ugraph component which represents a set of rows that
// can potentially be assembled into one wall.
struct Component {
#ifdef UNITTEST // is an actual real struct only when testing
    int one_node; // any node! idx in node_list (used to be GN*)
    Component* actual_component;
    compList::iterator graph_components_iterator_for_myself; // must be init'd
    // actual component refers to how merging causes a tree of comps to be
    // made. This allows the determination of which component a particular
    // given node belongs to a log-time operation rather than a linear one.

    D(int count;) // how many nodes I (should) have

    Component(): one_node(-1), actual_component(NULL) {
        D(count = 0;)
    }
#endif
};

#ifdef DEBUG
// a global pointer to the node list that makes it a little
// easier to reference it
std::vector<GraphNode> *node_list_ptr;

#  ifdef UNITTEST
std::ostream& operator<<(std::ostream& os, const Component& c) {
    os << "<s=" << c.count << ": 1_n=" << node_list_ptr->at(c.one_node).h_i;
    if (c.actual_component) {
        os << " ref=[" << *c.actual_component << "]";
    }
    os << ">";
    return os;
}
#  endif
#endif

コードの大きなブロックの場合、通常のブロック#ifdef条件を使用するだけであることに注意してください。これにより、読みやすさがいくらか向上します。大きなブロックの場合、非常に短いマクロを使用すると、より障害になります!

マクロが存在しなければならない理由は、単体テストが無効になっているときに追加N(x)するものを指定するためです。

この部分では:

U(GraphNode(uint64_t i, Component* c, int first_edge):)
N(GraphNode(uint64_t i, int first_edge):)

みたいな言い方ができたらいいですね

GraphNode(uint64_t i, U(Component* c,) int first_edge):

しかし、カンマはプリプロセッサ構文の一部であるため、できません。コンマを省略すると、無効な C++ 構文が生成されます。

デバッグ用にコンパイルしない場合の追加コードがある場合は、このタイプの対応する逆デバッグ マクロを使用できます。

さて、このコードは「本当に良いコード」の例ではないかもしれませんが、マクロを賢く適用することで達成できるいくつかのことを示しています。

私はちょうど今この宝石に出くわしdo{} while(0)ました.

私の例が、C++ コードを改善するために実行できる巧妙なことの少なくともいくつかについての洞察を提供できることを願っています。何が起こっているのか理解できないときに戻ってコードを実行するのではなく、コードを書いている間にコードを計測することは非常に価値があります。ただし、堅牢にすることと時間どおりに完了することとの間では、常にバランスを取る必要があります。

単体テストと同様に、追加のデバッグ ビルド サニティ チェックをツールボックス内の別のツールと考えるのが好きです。私の意見では、サニティ チェック ロジックを単体テストに入れて実装から分離するのではなく、それらが実装に含まれていて自由に呼び出すことができる場合、完全なテストはそれほど必要ではないため、それらはさらに強力になる可能性があります。ピンチで、チェックを有効にして通常どおりに実行できるためです。

于 2013-01-10T05:18:30.767 に答える
9

質問 1 の場合] 答えはイエスです。メッセージを標準エラーストリームに出力するだけです。

問2について】 たくさんあります。私のお気に入りは

#define LOG_ERR(...) fprintf(stderr, __VA_ARGS__)

これにより、任意の数の変数をデバッグ メッセージに含めることができます。

于 2013-01-10T05:22:36.400 に答える
8

私はマクロを__LINE__,コードのどこ__FILE__から出力したかを示す引数として使用するのが好きです.同じ変数名をいくつかの場所に出力することは珍しくありません.下。fprintf(stderr, "x=%d", x);

また、特定の関数をオーバーライドし、それが呼び出された場所 (メモリ割り当てなど) を保存するマクロも使用しました。メモリ割り当ての場合、C++ では new/delete を使用する傾向があり、簡単に置き換えることができないため、これは少し難しくなりますが、ロック/ロック解除操作などの他のリソースは、この方法でトレースするのに非常に役立ちます [もちろん、優れた C++ プログラマーのように構築/破棄を使用するロック ラッパーがある場合は、ロックを取得したら、それをコンストラクターに追加して、ファイル/行を内部構造に追加します。どこかで手に入れることはできません]。

于 2013-01-10T09:26:58.610 に答える
7

これは私が現在使用しているログ マクロです。

#ifndef DEBUG 
#define DEBUG 1 // set debug mode
#endif

#if DEBUG
#define log(...) {\
    char str[100];\
    sprintf(str, __VA_ARGS__);\
    std::cout << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << str << std::endl;\
    }
#else
#define log(...)
#endif

使用法:

log(">>> test...");

出力:

xxxx/proj.ios_mac/Classes/IntroScene.cpp][gotoNextScene][Line 58] >>> test...
于 2015-07-01T04:44:02.453 に答える
5

…そしてすべての回答の補遺として:

DEBUG個人的には、デバッグとリリース コードを区別するためにマクロを使用するNDEBUGことはありません。代わりに、呼び出しをなくすためにリリース ビルド用に定義する必要がassert()あるものを使用します (はい、assert()広く使用しています)。後者が定義されていない場合は、デバッグ ビルドです。簡単!したがって、実際にはもう 1 つデバッグ マクロを導入する理由はありません。(および両方が定義されていない場合に考えられるケースを処理しますDEBUG) NDEBUG

于 2015-06-04T13:51:07.377 に答える
4

これは、可変個引数テンプレートprint関数を使用した私のバージョンです。

template<typename... ArgTypes>
inline void print(ArgTypes... args)
{
  // trick to expand variadic argument pack without recursion
  using expand_variadic_pack = int[];
  // first zero is to prevent empty braced-init-list
  // void() is to prevent overloaded operator, messing things up
  // trick is to use the side effect of list-initializer to call a function
  // on every argument.
  // (void) is to suppress "statement has no effect" warnings
  (void)expand_variadic_pack{0, ((cout << args), void(), 0)... };
}

#ifndef MYDEBUG
#define debug_print(...)
#else
#define debug_print(...) print(__VA_ARGS__)
#endif

バージョンでは、debug_print実行時に出力する出力の種類を選択できるデバッグ レベルを受け入れる可変個引数テンプレート関数を作成します。

template<typename... ArgTypes>
inline void debug_print(debug::debug level, ArgTypes... args)
{
  if(0 != (debug::level & level))
    print(args...);
}

print関数が Visual Studio 2013 Preview をクラッシュさせることに注意してください(RC はテストしていません)。ostreamオーバーロードされた子クラスを使用した以前のソリューションよりも高速であることに気付きました (コンソール出力が遅い Windows では) operator<<

実際の出力関数を 1 回だけ呼び出したい (または独自のタイプセーフを作成する;-))場合は、一時的なstringstream内部を使用することもできます。printprintf

于 2013-09-30T15:48:36.570 に答える
3

ロギングには以下のコードを使用します。いくつかの利点があります。

  1. 実行時にオン/オフを切り替えることができます。
  2. 特定のログ レベルでステートメントをコンパイルできます。たとえば、現時点ではKIMI_PRIVATE、リリースビルドで何かをデバッグしているため、無条件にマクロでコンパイルしていますが、ログに記録されている可能性のある秘密のソースがたくさんあるため (笑)、リリースビルドからコンパイルしています。

このパターンは、長年にわたって私に非常に役立ってきました。注: グローバルlogMessage関数がありますが、通常、コードはログをロギング スレッドのキューに入れます。

#define KIMI_LOG_INTERNAL(level,EXPR)           \
  if(kimi::Logger::loggingEnabled(level))       \
  {                                             \
    std::ostringstream os;                      \
    os << EXPR;                                 \
    kimi::Logger::logMessage(level ,os.str());  \
  }                                             \
  else (void) 0

#define KIMI_LOG(THELEVEL,EXPR)                 \
  KIMI_LOG_INTERNAL(kimi::Logger::LEVEL_ ## THELEVEL,EXPR)

#define KIMI_ERROR(EXPR)   KIMI_LOG(ERROR,EXPR)
#define KIMI_VERBOSE(EXPR) KIMI_LOG(VERBOSE,EXPR)
#define KIMI_TRACE(EXPR)   KIMI_LOG(TRACE,EXPR)
#define KIMI_INFO(EXPR)    KIMI_LOG(INFO,EXPR)
#define KIMI_PROFILE(EXPR) KIMI_LOG(TRACE,EXPR)

// Use KIMI_PRIVATE for sensitive tracing
//#if defined(_DEBUG)
#  define KIMI_PRIVATE(EXPR) KIMI_LOG(PRIVATE,EXPR)
// #else
// #  define KIMI_PRIVATE(EXPR) (void)0
// #endif
于 2013-06-03T04:42:52.923 に答える