Home > ラーニング > セキュアコーディング > C セキュアコーディングスタンダード > 01. プリプロセッサ (PRE)
安全でないマクロ関数とは、コードに展開される際に引数を2回以上評価するか、あるいはまったく評価しないもののことである。代入、インクリメント、デクリメント、volatile アクセス、入出力、その他副作用(副作用を引き起こす可能性のある関数呼び出しを含む)を持つ引数を使って、安全でないマクロを呼び出さないこと。
安全でないマクロに関しては、その呼び出し時に副作用が発生する旨をドキュメントで警告しなければならないが、マクロを使用する責任はプログラマにある。このような使用時のリスクを考慮すると、安全でないマクロ関数の作成自体を避けるべきである(「PRE00-C. 関数形式マクロよりもインライン関数やスタティック関数を使う」を参照)。
assert() マクロは安全でないマクロの良い例である。このマクロの引数は、NDEBUG マクロの有無により、一度だけ評価されたり、あるいはまったく評価されなかったりする。詳細は、「EXP31-C. assert() の中では副作用を避ける」を参照。
安全でないマクロに関する問題の1つは、以下のコード例で示すように、マクロ引数に副作用があることである。
#define ABS(x) (((x) < 0) ? -(x) : (x)) /* ... */ m = ABS(++n); /* 未定義の動作 */
このコード例の ABS() マクロの呼び出しは、次のように展開される。
m = (((++n) < 0) ? -(++n) : (++n)); /* 未定義の動作 */
展開された結果のコードは「EXP30-C. 副作用完了点間の評価の順序に依存しない」に違反しており、未定義の動作となる。
以下のコードのように、安全でないマクロの呼び出し時に、代入、インクリメント、デクリメント、または関数呼び出しを含む引数を使わない解決法もある。
#define ABS(x) (((x) < 0) ? -(x) : (x)) /* 安全でないマクロ定義 */ /* ... */ ++n; m = ABS(n);
プログラマへの警告として、マクロが安全でないことがわかるようにコメントを残すこと。別の方法として、マクロ名を ABS_UNSAFE() に変更し、マクロが安全でないことがはっきりわかるようにするのもよい。しかし、より良い解決法は、ABS() をインライン関数として宣言することである(「PRE00-C. 関数形式マクロよりもインライン関数やスタティック関数を使う」を参照)。
inline int abs(int x) {
return (((x) < 0) ? -(x) : (x));
}
/* ... */
m = abs(++n);
このようにすれば、マクロが安全かどうか覚えておかなくてもよい。
PRE31-EX1: 副作用を持たない関数呼び出しは、このルールの例外として認められる。しかし、ソースコードが公開されていないライブラリ関数など、関数が隠し持つ副作用については忘れがちである。errno を変更することも副作用の1つである。関数が開発者自身によって書かれたものであり、単純に計算を行ってその結果を返し、他の関数を呼び出すことがないような場合を除いて、多くの開発者は副作用の存在を忘れてしまうだろう。したがって、この例外を適用することは認められるが、推奨されない。
副作用を持つ引数を使って安全でないマクロを呼び出すと、これらの副作用が何度も発生し、予期しないプログラム動作につながる可能性がある。
| ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
|---|---|---|---|---|---|
| PRE31-C | 低 | 低 | 中 | P2 | L3 |
LDRA ツールスイート V 7.6.0 でこのルールの違反を検出できる。