プリプロセッサ



プリプロセッサを使いこなそう

 プリプロセッサという考え方はC言語特有のものですが(多分)、うまく使えば実に強力なツールです。ここではプリプロセッサを使ったテクニックを紹介したいと思います。
 とは言え私もあまり知らないので皆さんの投稿をお待ちしております。



ヘッダファイルの二重読み込み禁止

 これはもはや常識となってしまった使い方ですが、ヘッダファイルの二重読み込み禁止方法です。ヘッダファイルの中からヘッダファイルを #include するということは良く行われますが、これをやると気をつけないと同じヘッダファイルが二回以上読み込まれてしまったり、ひどいときにはめぐりめぐって再帰的に読み込み続けてしまう構造になったりします。この問題はプリプロセッサ命令で簡単に回避できます。
 やり方は簡単で


#ifndef __FILENAME_H_
#define __FILENAME_H_

  <ヘッダファイルの中身>

#endif


 のようにヘッダファイルを #ifndef 文と #endif で囲ってやります。__FILENAME_H_ の部分は何でもいいのですが、ファイル毎に固有の名前を付けます。そうするともし2回目が #include されてもすでにその名前が #define されていますからそのファイルは丸々無視されます。



コメントアウト

 デバック中に大きなブロックをまとめてコメントアウトしたいときがありますが、そのブロックにすでにいくつか /* */ があった場合それらを消さないとコメントアウトできません。コンパイラによっては /* */ のネストを許すというものもありますが、ANSI-Cの規格から外れるためポータビリティーが低下しますし、私の経験で「とあるライブラリのヘッダがコメントのネスト許したとたんにエラーを吐いた」などという間抜けなこともあります。ではどうすればいいかと言うと、すかさず


#if 0

  <コメントアウトしたい部分>

#endif


 とやってしまいます。これでこのブロックのコメントアウトができますよね。外に公開するソースにこういう部分を残すことは無いのでまず見かけることのないテクニックですがデバック中に「ちょっと試しに」というときにはよくやる技です(私だけか?)。意外とコロンブスの卵で私も人に教えてもらうまで気づきませんでした。
 でももっといい方法もありそうな気がも.....
 良い方法ご存知の方投稿してください。


_DEBUG マクロを使おう

 プログラムをデバックするときにしばしば変数の内容を printf で出力してみたり、実行状態をログファイルに吐かせたりなどといろんなことをしますよね。でもデバックのたびにこれらを入れたり、コメントアウトしたりを繰り返すのは非常に面倒です。そこですかさず使うのが _DEBUG マクロです。まあ名前は何でもいいのですがこんな感じの名前をよく見かけます。でもってデバック用のコードを


#ifdef _DEBUG

  <デバック用のコード>

#endif


とするというただこれだけのことです。
 しかしながら _DEBUG というマクロを定義するかどうかだけでそのような機能の一括追加/削除が可能となり大変便利です。

 折角なんでどう言うものを埋め込むと便利かいくつか考えてみました。


変数の表示

 一番よくやることではないでしょうか。最近はデバッカがとかく強力なのでする必要もなくなりつつありますが、昔はよくやりました(マイコン開発では毎日やってる貧乏な私)。


実行のトレース

 要するに実行状態をログファイルに吐かせることによって実行状態を追いかけます。関数の呼び出し頻度を調べるとか、メッセージのログを残すとか、リソースの確保、開放をトレースしてリークを見極めるとか、使い方次第で有用な情報を吐き出す機能を追加することが出来ます。

変数範囲のチェック

 プログラムのモジュール開発が頻繁になってくるとモジュール側に呼び出し側のミスによるエラーを検出する機構をつけたくなります。要するにモジュール呼び出し時のパラメーターチェックなのどが主なのですが、これに引っかかるものはすべてプログラマのミスであるためデバックが完了してしまうとこれらはただの無駄でしかありません。そこでこのようなコードもデバック時のみ組み込みます。簡単なのは ASSERT マクロを作ってしまうことです。



ASSERT マクロを使おう

 先ほどの続きですが ASSERT マクロというものを作っておくとデバック時に便利です。MFCなどのライブラリでは当たり前のように実装されていますが、これらのライブラリを使わない場合でも簡単に作れますのでガンガン利用しましょう。
 DOSやUNIXのようなコンソール環境だと以下のような感じで宣言すればいいでしょう。


#ifdef _DEBUG
#define ASSERT(f) \
  if ( !(f) ) { \
    fprintf(stderr, "Assertion Failed! %s (%d)\n", __FILE__, __LINE__); \
    exit(1); \
  }
#else
#define ASSERT(f) ((void)0)
#endif


 このような感じで ASSERT マクロを作っておいて、関数の入り口などで


void func(int* pData, int nIndex)
{
  ASSERT(pData);
  ASSERT(nIndex >= 0 && nIndex < nDataLength);
  
  <関数の処理>
}


 のように使えば pData が NULL であったり、nIndex が範囲外であればファイル名と行番号を表示してプログラムが異常終了するわけです。このようなミスは明らかに呼び出し側のバグによるものですからバグを修正すればこれらのチェックは不要となりますので最後に _DEBUG を定義せずにコンパイルし、速度とサイズの最適化を計るわけです。