0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

デバッグ・テクニック

Posted at

printデバッグをしていると、出力が多すぎて困る事がある。
単にif文を入れて済めば良いが、それでは制御しきれない事も多々ある。

その様なケースでの、幾つか対処法を紹介する。

note

  • 言語は C++ を想定しているが、他の言語でも流用は出来るはす。

エラー発生の前でのみデバッグ

状況設定

  • 何度目かの呼び出しで、エラーが発生する。
    • 他の関数を挟んで呼び出しているため、呼び元(ループ側)での制御はしずらい。
  • エラーが発生する少し前の状態を知りたい。
void fn_3()
{
  ...
  Trace(data); // 変更前の、この時点での情報が欲しい。
  ...          // data が変更される。
  data.foo();  // ここでエラーが発生する。
}

void fn_2(){ fn_3(); }
void fn_1(){ fn_2(); }

void fn()
{
  for(...){
    fn_1();
  }
}

解決策

関数内stasic変数で、関数の呼び出し回数が分かるようにし、
エラー発生時点での関数の呼び出しが何回目かを記録する。

void fn_3()
{
+ static int debug_counter = 0;
+ ++debug_counter;

  ...
+ Trace(debug_counter); // エラー発生のタイミングを確認
  data.foo();           // エラー発生時に、何らかの方法で処理を止める。
}

何度目の関数呼び出しでエラーが発生するかが分かったら、
そのタイミングでのみ、Traceさせる。

void fn_3(){
+ static int debug_counter = 0;
+ ++debug_counter;

  ...

+ if ( counter == 193 ) { // 事前に確認した値。
    Trace(data);
+ }

  ...
  data.foo();
}

継承先でのデバッグ

状況設定

以下の様に、ID番号を共通の親クラスで管理しているとする。

class Element {
public:
  void SetID( const int id ) { m_id = id; }
  ...
private:
  int m_id{};
};

class Impl_A : public Element {...};
class Impl_B : public Element {...};
class Impl_C : public Element {...};

Impl_A での m_id の値が可怪しいので、SetID のタイミングでデバッグをしたいが、
SetID にトレース文を入れると、Impl_B,Impl_CSetID でもログが出てしまう。

解決策

  • SetID を仮想関数にし、Impl_A でのみオーバーライドをする。
    • 実装は、親の関数を呼ぶようにする。
class Common {
public:
-  void SetID( const int id ) { m_id = id; }
+  virtual void SetID( const int id ) { m_id = id; }
  ...
private:
  int m_id{};
};

class Impl_A : public Common {
+   void SetID( const int id ) override {
+     Trace( id );
+     Common::SetID(id);
+   }
  ...
};
class Impl_B : public Common {...};
class Impl_C : public Common {...};

特定呼び出しからのデバッグ

状況設定

特定の場所から呼ばれた時のみ、デバッグをしたい。

void fn_3( ... )
{
  Trace(...); // fn_A から呼ばれた時のみ、Trace したい。
}

void fn_2( ... ){ fn_3( ... ); }
void fn_1( ... ){ fn_2( ... ); }

void fn_A() { fn_1( ... ); }
void fn_B() { fn_1( ... ); }
void fn_C() { fn_1( ... ); }

引数にフラグを追加する、では、変更量が多くて大変。

  • オーバーロードが有ると、処理を変えてしまう可能性もある。
void fn_3( ..., const bool debug_flag = false )
{
  if ( debug_flag ) {
    Trace(...); // fn_A から呼ばれた時のみ、Trace したい。
  }
}

void fn_2( ..., const bool debug_flag = false )
{
  fn_3( ..., debug_flag );
}
void fn_1( ..., const bool debug_flag = false )
{
  fn_2( ..., debug_flag );
}

void fn_A() { fn_1( ..., true ); }
void fn_B() { fn_1( ... ); }
void fn_C() { fn_1( ... ); }

解決策

コードをコピーし、同じ実装の、別の関数を作れば良い。

void fn_3( ... )
{
}

void fn_2( ... ){ fn_3( ... ); }
void fn_1( ... ){ fn_2( ... ); }

+namespace ForDebug {
+
+void fn_3( ... )
+{
+  Trace(...);
+}
+
+void fn_2( ... ){ fn_3( ... ); }
+void fn_1( ... ){ fn_2( ... ); }
+
+} // ForDebug

-void fn_A() { fn_1( ... ); }
+void fn_A() { ForDebug::fn_1( ... ); }
void fn_B() { fn_1( ... ); }
void fn_C() { fn_1( ... ); }
  • ビルドが通らない場合は、関連コードを片っ端からコピーして持ってくる。
  • static 変数が絡む場合は、処理が変わってしまう事に留意。

おまけ

  • 通常は使用を避けるべき、コードのコピーやstatic変数だが、この様な場合は便利に使え。る
    • デバッグ用の使い捨てコードなので、諸々のデメリットも気にしなくて済む。
  • デバッグ用のコードで使う識別子は、うっかり Push してしまった場合に備えて、
    それと分かる様な名前にしておくとベター。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?