見出し画像

コンパイルスイッチ

C言語にはコンパイルスイッチ(条件付きコンパイル)という機能があります。OSやプラットホーム、ハードウェアの違いによる定義や処理の違いを切り替えるために多用されます。しかし不適切なコンパイルスイッチの使い方をして不具合を招いた例です。

#if NUMBER_OF_OPTIONAL_LINES > 3
  /* optional line の数が 3 を超えるときの処理 */
#else
  /* optional line の数が 3 以下のときの処理 */
#endif

上記のコードは NUMBER_OF_OPTIONAL_LINES が define定義されているときには期待通りにコンパイルされ、かつ、動作していました。しかしソースコードを引き継いだ担当者が NUMBER_OF_OPTIONAL_LINES のdefine定義の記述があるヘッダファイルへのinclude文を書き漏らしました。include文を書き漏らした結果、NUMBER_OPTIONAL_LINESマクロは『未定義』となりました。その結果、未定義の識別子は "0L (long型のゼロ)" に展開するというC言語のトリビア的な仕様で期待とは異なる結果にコンパイルされました。期待とは異なりますがC言語の仕様としては正しい振る舞いであることが厄介でした。

もしも以下のように optional line の数を const 変数 で記述していればコンパイルエラーで記述漏れに気づけたかもしれません。しかし「条件付きコンパイル」はその名の通り条件が成立しないときはコンパイルの対象とならないため、コンパイラはエラーもワーニングも発しませんでした。

const int NUMBER_OF_OPTIONAL_LINES = 5;

if (NUMBER_OF_OPTIONAL_LINES > 3) {
  /* optional line の数が 3 を超えるときの処理 */
} else {
  /* optional line の数が 3 以下のときの処理 */
}

ちなみに gcc  には未定義のマクロの 0L 置換を警告するオプション -Wundef があります。


次は誤読を招くコンパイルスイッチの使い方をしていた例です。

#ifdef USO800
    /* モデル名 USO800 が定義されているときに実行するコード */
#endif /* USO800 */
#ifndef USO800
    /* モデル名 USO800 が定義「されていない」ときに実行するコード */
#endif /* USO800 */

モデル名 USO800 によるコンパイルスイッチ(条件付きコンパイル)が十数箇所にわたって書き散らされていました。機種依存するコードがソースファイルの複数箇所に散らばっていることがそもそも設計上の悪手なのですが、たまたま、その十数箇所のコンパイルスイッチの一箇所が #ifndef を使っていたため、その後のコード書き換えで間違ってしまいました。

コンパイルスイッチ(条件付きコンパイル)はコンパイル(プリプロセス)のタイミングで判定処理や数値演算を実行するため、ターゲットCPUで実行したときの処理を軽くする目的で組込みシステムで多用される傾向にあります。しかしながら、コンパイルスイッチで実装した内容はコンパイラのチェックをほぼ受けないため、慎重に記述しないと後日思いもしない不具合を招きます。


ちなみにコンパイルスイッチの中にコンパイルスイッチが入ったネストであったり、コンパイルスイッチの中で新たなマクロを定義して、コンパイルスイッチの連鎖が起こっているときには、どのマクロ定義、どの条件付きコンパイルが成立するか一目ではわからないときがあります。そういったときは次のように #error ディレクティブ を使うとコンパイラ(プリプロセッサ)がエラーを発してマクロ展開の結果をコンパイル結果の中で表示できます。

#ifndef DISABLE_X_SENSOR
#if NUMBER_OF_X_SENSORS > 3
#error "X SENSOR is greater than 3"
#else
#error "X SENSOR is not less than or equal to 3"
#endif
#else
#error "X SENSOR is disabled"
#endif


この記事が気に入ったらサポートをしてみませんか?