デバッグの効率を劇的に改善するそのやり方とは…?
動作確認のためにprintfを書いたり消したりするのがめんどくさい!
作ったプログラムが正しく動いているか確認するために、途中結果を表示したいときってありますよね。
でも、確認のためだけにprintfを書いたり消したりするのって、かなり面倒くさくありませんか?
でも、今日でそんな面倒くさいことは終わりです。
C言語にはデバッグのときだけ、printf関数を有効にする方法があるんですよ。
本記事ではその方法をご紹介しますので、ぜひ今後のプログラミングに役立ててみてくださいね。
気になるところへGO!
デバッグのときだけprintf関数を有効にする方法

途中計算などをデバッグのときにだけ表示させたい場合は、『マクロ』と『条件コンパイル』を使えばOKです。
具体的には次のようなプログラムをmain関数の前(ヘッダー)に記載する方法になります。
#ifdef DEBUG
#define DEBUG_PRINTF("いいね!\n) printf("いいね!\n")
#else
#define DEBUG_PRINTF("いいね!\n)
#endif
表示・非表示に役立つ『条件付きコンパイル』

先ほどの文章は『条件付きコンパイル』という方法を実現するためのプログラムです。
プログラムの内容は次のような意味を表しています。
#ifdef DEBUG
//もし『DEBUG』というマクロが定義(define)されていたら…
#define DEBUG_PRINTF("いいね!\n) printf("いいね!\n")
//『DEBUG_PRINTF("いいね!\n) 』を『printf("いいね!\n")』に置き換えてコンパイルしてね。
#else
//もし『DEBUG』というマクロが定義(define)されていなかったら…
#define DEBUG_PRINTF("いいね!\n)
//『DEBUG_PRINTF("いいね!\n)』は空白に置き換えてコンパイルしてね。
#endif
//おわり
この方法の致命的な欠点

先ほどご紹介したプログラムの『いいね!』の部分を変更すれば、デバッグのときだけ任意の文字を表示することができます。
しかし、この方法には致命的な欠点があるのです。
先ほどのプログラムではデバッグのときだけ『いいね!』を表示するプログラムでした。
ではデバッグの時に『いいね!』のほかに『やばいよ!』も表示させたい場合はどうなるのでしょうか?
回答としては次のようなプログラムになります。
#ifdef DEBUG
#define DEBUG_PRINTF("いいね!\n) printf("いいね!\n")
#define DEBUG_PRINTF("やばいよ!\n) printf("やばいよ!\n")
#else
#define DEBUG_PRINTF("いいね!\n)
#define DEBUG_PRINTF("やばいよ!\n)
#endif
デバッグの時に表示させたい文章が1つや2つということは、ほとんどないでしょう。
なので、この方法だとソースファイルのヘッダー部分が、大量のDEBUGプログラムで埋め尽くされてしまうのです。
真・デバッグのときだけprintf関数を有効にする方法

先ほどご紹介したプログラムには致命的な欠点がありましたが、実はこの欠点を簡単に回避する方法があります。
その方法とは先ほどのプログラムを次のプログラムに変更することです。
#ifdef DEBUG
#define DEBUG_PRINTF printf
#else
#define DEBUG_PRINTF 1 ? (void)0 : printf
#endif
このプログラムは冒頭でご紹介したプログラムと違って、『DEBUG_PRINTF』の部分だけをprintfに変換するようにしています。
プログラムにDEBUG_PRINTFのカッコの中身を書かないことで、カッコの中身を臨機応変に変更できるようにしているのです。
つまり、このプログラムを使えばカッコの中に何が書かれていようとも、printf関数として実行されることになります。
この方法のもう1つのメリット

実は先ほどのプログラムのメリットはプログラムの行数が増えないことだけではありません。
ソフトウェアの品質にほとんど影響を与えないことも大きなメリットなのです。
というのも、先ほどのプログラムは『マクロ』と『条件付きコンパイル』のほかに『条件演算子』を使っているので、このような付加価値が生まれるんですよ。
先ほどのプログラムの中で条件演算子は『1 ? (void)0 : printf』の部分が該当します。
そして、条件演算子は次のような効果を持つ表記方法なのです。
書き方: A ? B : C
意味:Aが真(=1)ならB、偽(!=1)ならC
先ほどのプログラムの条件演算子をもう一度見てみましょう。
『DEBUG_PRINTF 1 ? (void)0 : printf』でAの部分に当てはまるのは『1』であることが分かります。
つまり、この条件演算子は常に真となるので、DEBUG_PRINTFはBの位置にある『(void)0』に置き換えられるのです。
『#define DEBUG』がない場合、『いいね!』を表示する『DEBUG_PRINTF(“いいね!\n”);』は次のようになります。
(void)0("いいね!\n");
すると、次の論文が示すように、このような表記をするとプログラムを無効化することができるのです。
If an expression of any other type is evaluated as a void expression, its value or designator is discarded.
この論文に書かれているC言語の正式ルールによれば、『#define DEBUG』がない場合、この条件演算子の計算結果は存在しないことになります。
しかも、この条件演算子はAが必ず1になるため、Cの部分にあるprintfに置き換えられることは絶対にありません。
つまり、この部分のプログラムは『#define DEBUG』を書かない限り、存在しないことになるのです。
そのため、コンパイラの最適化機能によって、デバッグでない時はDEBUG_PRINTFに関するプログラム自体が消滅することになります。
参考:よく使われるDEBUG_PRINTFの表記方法との比較

先ほどご紹介した方法のほかに、よく使われるDEBUG_PRINTFの使い方は下記のとおりです。
#ifdef DEBUG
#define DEBUG_PRINTF(fmt, ...) printf(fmt, __VA_ARGS__)
#else
#define DEBUG_PRINTF(fmt, ...)
#endif
この方法はprintf関数のカッコの中身にどのような情報が書かれているかを示しています。
つまり、printf(“%d+%d\n”,100,10);のような命令を実行できるのです。
しかし、この方法だと引数が必要なprintf関数しか使用できないという欠点があります。
まとめ
以上がデバッグの時にだけprintf関数を有効にする方法になります。
この方法を知っておけば、プログラムの実装と並行してD_PRINTFを書くことができますし。
『#define DEBUG』をコメントアウトするだけで、表示・非表示を切り替えることができます。
この方法を使えば、効率的にバグ出しをすることができるようになるので、ぜひ使ってみてくださいね!
- デバッグの時だけ途中計算などを表示させる方法はある
- その場合はマクロと条件付きコンパイルを使用する
- しかし、その方法ではプログラムの量がたくさん増えてしまうので、条件演算子を使うと良い