Edited at

なにはともあれdbgprintfを定義しておくと便利、という話を丁寧に説明してみました。

C言語のお話。こんにちは、@island_peakです。本記事のゴールはコチラ。


  • ビルドを切り替えるだけで、不具合の原因を特定できるスマートな大人になれる

  • 可変長引数関数(printfなど)のマクロ定義方法(define)を理解できる

#define <stdio.h>

dbgprintf("hello,printf debugging!"
"(%d) as %s\n", hoge, test);

様々な開発ツールが登場している2019年。未だ、僕の中での最強のデバッグツールは、printfです。OSよりも低いレイヤーを開発していると、デバッガすら接続できない領域も多く 値(状態)を表示できる printf は必需品です。とはいえ常にデバッグ用のメッセージが出ているのもイマイチ…ということで見たいときだけ表示できるdbgprintfの実装方法を紹介いたします。

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3332393736332f38353761343337342d373035352d326432382d386666662d3562636136366162666435352e6a706567.jpeg


dbgprintfの定義方法 3種類

@island_peak がよく使うdbgprintfの実装パターンは、以下の3種類です。


  • ビルド時にON/OFFできるdbgprintf

  • 実行中にON/OFFできるdbgprintf

  • 実行中に指定したレベル以上のみを表示できるdbgprintf


ビルド時にON/OFFできるdbgprintf

C言語では#defineで可変長引数を使うことができます。#defineの置換元でf_, ...と書いておくと、置換後の文字列(f_), ##__VA_ARGS__で可変長部分を展開することができます。dbgprintfは開発時のみ使う機能のため、ON/OFFできるようにしましょう。#ifdefを使って#defineの置換方法を切り替えます。while(0){}何もしない処理です(アセンブラ命令レベルで処理が増えません)。


debug.h

#ifndef _DEBUG_H_

#define _DEBUG_H_

/* DEBUG = ON/OFF */
#define _DEBUG_PRINTF_

#ifdef _DEBUG_PRINTF_
#define dbgprintf(f_, ...) printf((f_), ##__VA_ARGS__)
#else
#define dbgprintf(f_, ...) while(0){}
#endif
/* _DEBUG_PRINTF_ */

#endif /* _DEBUG_H_*/

スイッチアイコン0.png


実行中にON/OFFできるdbgprintf

先の例ではdbgprintfprintfに置換しましたが、while(条件式){... break;}に置換することにより、条件式にあわせてON/OFFできるプリント文を作ることができます。以下の例では、広域変数dbgOnにtrueが代入されている場合のみ出力されます。


debug.h

#ifndef _DEBUG_H_

#define _DEBUG_H_

/* DEBUG = ON/OFF */
#define _DEBUG_PRINTF_

#ifdef _DEBUG_PRINTF_
extern bool dbgOn;
#define dbgprintf(f_, ...) while(dbgOn){ printf((f_), ##__VA_ARGS__); break; }
#else
#define dbgprintf(f_, ...) while(0){}
#endif
/* _DEBUG_PRINTF_ */

#endif /* _DEBUG_H_*/


ソースコード側

/** main.cなどに1回だけ定義する */

#ifdef _DEBUG_PRINTF_
bool dbgOn = true;
#endif /* _DEBUG_PRINTF_ */
...
/** 使い方 */
dbgprintf("ここはでます");
...
dbgOn = false;
dbgprintf("ここはでません");
...
dbgOn = true;
dbgprintf("ここはでます");


デバッガと併用する裏技

上例ではソースコードがdbgOntrueを代入した区間のみ表示する、と紹介しました。が、デバッガを接続しdbgOnの値を書き換えても表示のON/OFFができます。常にdbgOn=falseとし、不具合解析の際に自分で切り替えるという使い方も便利です。

スイッチアイコン.png


実行中に指定したレベル以上のみを表示できるdbgprintf

while(条件式){... break;}の条件式にtrue/falseだけでなく、数値比較を入れることにより、プリント文を変更することができます。下記のコードはdbgprintf(Lv, "hoge");のLvに従い、広域変数dbgLevelにあわせて出力を調整する例です。OSカーネルで利用されるprintkに似た機能を使うことができます。

スイッチアイコン2.png


debug.h

#ifndef _DEBUG_H_

#define _DEBUG_H_

/* DEBUG = ON/OFF */
#define _DEBUG_PRINTF_

#ifdef _DEBUG_PRINTF_
extern int dbgLevel;
#define dbgprintf(lv, f_, ...) while(dbgLevel<=lv){ printf((f_), ##__VA_ARGS__); break; }
#else
#define dbgprintf(lv, f_, ...) while(0){}
#endif
/* _DEBUG_PRINTF_ */

#endif /* _DEBUG_H_ */


ソースコード側

/** main.cなどに1回だけ定義する */

#ifdef _DEBUG_PRINTF_
int dbgLevel = 0;
#endif /* _DEBUG_PRINTF_ */
...
/** 使い方 */
dbgprintf(1, "ここはでます");
dbgprintf(2, "ここはでます");
...
dbgLevel = 2;
dbgprintf(1, "ここはでません");
dbgprintf(2, "ここはでます");
...
dbgLevel = 3;
dbgprintf(1, "ここはでません");
dbgprintf(2, "ここはでません");


まとめ

いかがだったでしょうか。小さい機能を試し書きする際など、手っ取り早く書くときはdbgprintfが便利です。すべてのコンパイラでビルドできる点もオススメポイントです。ではでは。