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つの引数では、Cで可変数のマクロ引数を必要とするものが得られます。引数にセミコロンを含めることで、人々がそれを乱用する可能性があります。または、コール後に忘れられたセミコロンによるエンコーターのミスですらあります。したがって、これをdoブロックに含めます。
#define DEBUG(x) do { std::cerr << x; } while (0)
私は上記のものが好きで、かなり頻繁に使用します。私のノーオペレーションは通常読むだけです
#define DEBUG(x)
コンパイラの最適化と同じ効果があります。以下の@Tony Dによるコメントは誤りですが、これにより一部の構文エラーが検出されないままになる可能性があります。
ランタイムチェックを含めることもあるので、何らかの形でデバッグフラグを提供します。 @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__
を含めるのが好きですが、これらはより頻繁にアサーションであり、単純なデバッグマクロではありません。
これが私のお気に入りです
_#ifdef DEBUG
#define D(x) x
#else
#define D(x)
#endif
_
それは非常に便利で、きれいな(そして重要なことに、リリースモードで高速!!)コードを作成します。
デバッグ関連のコードブロックを除外するためのあちこちにたくさんの_#ifdef DEBUG_BUILD
_ブロックがありますが、数行をD()
でラップするとそれほど悪くありません。
使い方:
_D(cerr << "oopsie";)
_
それでもforい/奇妙な/長すぎる場合は、
_#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
_
(I 提案 _using namespace std;
_を使用していないが、おそらく_using std::cout; using std::cerr;
_が良い考えかもしれない)
"デバッグ"を考えているときは、stderrに印刷するだけでなくより多くのことをしたいかもしれないことに注意してください。創造性を身に付ければ、プログラム内の最も複雑な相互作用に関する洞察を提供する構造を構築できます。また、デバッグ計装に邪魔されない非常に効率的なバージョンの構築にすばやく切り替えることができます。
たとえば、最近のプロジェクトの1つでは、FILE* file = fopen("debug_graph.dot");
で始まる巨大なデバッグ専用ブロックがあり、その中にある大きなツリーを視覚化するために graphviz 互換性のあるグラフをドット形式でダンプしました私のデータ構造。さらにクールなのは、OS X graphvizクライアントが変更されたときにファイルをディスクから自動読み取りするため、プログラムが実行されるたびにグラフが更新されることです!
また、デバッグ専用のメンバーと関数でクラス/構造体を「拡張」することも特に好きです。これにより、バグを追跡するのに役立つ機能と状態を実装する可能性が開かれ、デバッグマクロにラップされている他のすべてのものと同様に、ビルドパラメーターを切り替えることで削除されます。状態の更新ごとに各コーナーケースを入念にチェックする巨大なルーチンですか?問題ない。 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)
マクロが存在しなければならない理由は、単体テストがdisabledの場合にaddに何を追加するかを指定するためです。 。
このパートでは:
_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++構文が生成されます。
デバッグ用にコンパイルするときnotの追加コードがある場合は、このタイプの対応する逆デバッグマクロを使用できます。
今、このコードは「本当に良いコード」の例ではないかもしれませんが、マクロの巧妙なアプリケーションで達成できることのいくつかを示しています。もしあなたが規律を守れば、必要ではありません 悪の。
do{} while(0)
のことを考えた直後に this gem に出会いましたが、これらのマクロにもそのようなすべての空想が必要です!
私の例が、C++コードを改善するためにできる少なくともいくつかの賢いことについての洞察を提供できることを願っています。何が起きているのか分からないときに戻ってコードを書くよりも、コードを書いている間にコードを計測することは本当に価値があります。しかし、それは常に、堅牢にすることと期限内に完了することの間のバランスを取る必要があります。
追加のデバッグビルドの健全性チェックは、単体テストと同様に、ツールボックス内の別のツールとして考えるのが好きです。私の意見では、健全性チェックのロジックを単体テストに入れて実装から分離するのではなく、実装に含まれていて自由に呼び出すことができる場合、完全なテストは必要ではないため、さらに強力になる可能性があります簡単にチェックを有効にして、いつものようにピンチで実行できるからです。
質問1について]答えはイエスです。メッセージを標準エラーストリームに出力するだけです。
質問2]がたくさんあります。私のお気に入りは
#define LOG_ERR(...) fprintf(stderr, __VA_ARGS__)
これにより、任意の数の変数をデバッグメッセージに含めることができます。
__LINE__
、__FILE__
を含むマクロを引数として使用して、印刷出力元のコードでwhereを表示するのが好きです。同じ変数名を複数の場所に印刷することはまれなので、fprintf(stderr, "x=%d", x);
はさらに10行下に別の変数を追加しても意味がありません。
また、特定の機能をオーバーライドし、それが呼び出された場所(メモリ割り当てなど)を保存するマクロを使用したため、後でリークしたものを特定できます。 C++では、メモリの割り当てについては、new/deleteを使用する傾向があり、簡単に置き換えることができないため、これは少し難しくなりますが、ロック/ロック解除操作などの他のリソースは、この方法を追跡するのに非常に役立ちます優れたC++プログラマーのように構築/破壊を使用するロックラッパーがある場合は、コンストラクターに追加して、ロックを取得したら内部構造にファイル/行を追加し、いつどこで保持されているかを確認できますどこかで手に入れることができません]。
これは私が現在使用しているログマクロです。
#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...
これは可変バージョンテンプレート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プレビューをクラッシュさせることに注意してください(RCはテストしていません)。 ostream
子クラスをオーバーロードしたoperator<<
。
実際の出力関数を1回だけ呼び出したい場合(または、独自のタイプセーフstringstream
;-)を作成したい場合は、print
内で一時的なprintf
を使用することもできます。
ロギングには以下のコードを使用します。いくつかの利点があります。
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
私は次のマイクロを使用し、
#if DEBUG
#define LOGE2(x,y) std::cout << "ERRO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y <<std::endl;
#define LOGI2(x,y) std::cout << "INFO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y << std::endl;
#define LOGD2(x,y) std::cout << "DEBG : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y << std::endl;
#define LOGE(x) std::cout << "ERRO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#define LOGI(x) std::cout << "INFO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#define LOGD(x) std::cout << "DEBG : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#else
#define LOGE2(x,y) NULL
#define LOGI2(x,y) NULL
#define LOGD2(x,y) NULL
#define LOGE(x) NULL
#define LOGI(x) NULL
#define LOGD(x) NULL
#endif
つかいます:
LOGE("ERROR.");
LOGE2("ERROR1","ERROR2");