プログラミングのコツ

【オススメ】デバッグに超便利!デバッグの時だけprintfを発動する方法とは…?

トビタカ
トビタカ
本記事では、デバッグのときだけ途中計算などをprintf関数で表示する方法をご紹介しています。

デバッグの効率を劇的に改善するそのやり方とは…?

 

動作確認のためにprintfを書いたり消したりするのがめんどくさい!

 

作ったプログラムが正しく動いているか確認するために、途中結果を表示したいときってありますよね。

でも、確認のためだけにprintfを書いたり消したりするのって、かなり面倒くさくありませんか?

とび助
とび助
本当に面倒くさいです…
トビタカ
トビタカ
しかも、消したとたんにバグが出たりするんですよね…

 

でも、今日でそんな面倒くさいことは終わりです。

C言語にはデバッグのときだけ、printf関数を有効にする方法があるんですよ。

本記事ではその方法をご紹介しますので、ぜひ今後のプログラミングに役立ててみてくださいね。

トビタカ
トビタカ
効率が段違いなので、ぜひ活用してみてくださいね!
とび助
とび助
本当ですか!早く教えてください!

デバッグのときだけ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
  //おわり

 

トビタカ
トビタカ
つまり、このプログラムの前に『#define DEBUG』を書いたときにだけ、『いいね!』が表示されるのです!
とび助
とび助
なるほど!『#define DEBUG』を書いたり消したりするだけで、表示・非表示を切り替えられるんですね…?
トビタカ
トビタカ
そういうことになります!

この方法の致命的な欠点

先ほどご紹介したプログラムの『いいね!』の部分を変更すれば、デバッグのときだけ任意の文字を表示することができます。

しかし、この方法には致命的な欠点があるのです。

とび助
とび助
致命的な欠点ですか…?
トビタカ
トビタカ
はい。表示する文章の数だけ先ほどのプログラムが必要になってしまうのです。

 

先ほどのプログラムではデバッグのときだけ『いいね!』を表示するプログラムでした。

ではデバッグの時に『いいね!』のほかに『やばいよ!』も表示させたい場合はどうなるのでしょうか?

回答としては次のようなプログラムになります。

#ifdef DEBUG
#define DEBUG_PRINTF("いいね!\n) printf("いいね!\n")
#define DEBUG_PRINTF("やばいよ!\n) printf("やばいよ!\n")
#else
#define DEBUG_PRINTF("いいね!\n)
#define DEBUG_PRINTF("やばいよ!\n)
#endif

 

とび助
とび助
2行も増えてる…
トビタカ
トビタカ
つまり、プログラムの量が『表示する文章の2倍だけ増える』のです。

 

デバッグの時に表示させたい文章が1つや2つということは、ほとんどないでしょう。

なので、この方法だとソースファイルのヘッダー部分が、大量のDEBUGプログラムで埋め尽くされてしまうのです。

とび助
とび助
これじゃあ使えないじゃないですか…
トビタカ
トビタカ
大丈夫です!少し工夫するだけで効率が劇的に改善しますよ!

真・デバッグのときだけprintf関数を有効にする方法

先ほどご紹介したプログラムには致命的な欠点がありましたが、実はこの欠点を簡単に回避する方法があります。

その方法とは先ほどのプログラムを次のプログラムに変更することです。

#ifdef DEBUG
#define DEBUG_PRINTF printf
#else
#define DEBUG_PRINTF 1 ? (void)0 : printf
#endif

 

とび助
とび助
なんかスタイリッシュになったうえに、4行目に変な文章がありますね…
トビタカ
トビタカ
これからその部分をご紹介しますね!

 

このプログラムは冒頭でご紹介したプログラムと違って、『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』に置き換えられるのです。

とび助
とび助
この(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.

引用:J Gustedt2021-proposal for C23

 

とび助
とび助
英語なんで分かりません!
トビタカ
トビタカ
void型にキャストされた場合、結果を破棄するということが記されています。

 

この論文に書かれているC言語の正式ルールによれば、『#define DEBUG』がない場合、この条件演算子の計算結果は存在しないことになります。

しかも、この条件演算子はAが必ず1になるため、Cの部分にあるprintfに置き換えられることは絶対にありません。

つまり、この部分のプログラムは『#define DEBUG』を書かない限り、存在しないことになるのです。

そのため、コンパイラの最適化機能によって、デバッグでない時はDEBUG_PRINTFに関するプログラム自体が消滅することになります。

トビタカ
トビタカ
この最適化機能によって、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(“いいね!\n”);は引数がないので使えないんですよ。
とび助
とび助
それはちょっと使いづらいですね…
トビタカ
トビタカ
なので、ご紹介した方法が最強ということです!

 

まとめ

以上がデバッグの時にだけprintf関数を有効にする方法になります。

この方法を知っておけば、プログラムの実装と並行してD_PRINTFを書くことができますし。

『#define DEBUG』をコメントアウトするだけで、表示・非表示を切り替えることができます。

この方法を使えば、効率的にバグ出しをすることができるようになるので、ぜひ使ってみてくださいね!

とび助
とび助
これはすぐに使わせていただきます!
トビタカ
トビタカ
素晴らしいです!実はもっといい方法もあるので、また今度ご紹介しますね!
まとめ
  • デバッグの時だけ途中計算などを表示させる方法はある
  • その場合はマクロと条件付きコンパイルを使用する
  • しかし、その方法ではプログラムの量がたくさん増えてしまうので、条件演算子を使うと良い

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA