DCL20-C. 引数を受け付けない関数の場合も必ず void を指定する
C 標準のセクション 6.7.6.3 第 14 段落 には次のように記載されている [ISO/IEC 9899:2011]。
識別子並びは、関数の仮引数の識別子だけを宣言する。関数定義の一部である関数宣言子で識別子並びが空の場合、関数が仮引数をもたないことを指定する。関数定義の一部でない関数宣言子の識別子並びが空の場合、仮引数の個数及び型の情報がないことを指定する。
また、セクション 6.11.6 には以下のように記述されている。
空の括弧を伴う関数宣言子(関数原型形式の仮引数型並びではない。)の使用は、廃止予定事項とする。
ゆえに、引数を受け付けない関数は、引数リストにおいて void
を明示的に宣言する必要がある。これは、関数の宣言と定義(一致しているはずである)のどちらの部分にも当てはまる。
void
の引数リストを指定して関数を定義することは、関数を引数なしとして宣言することとは異なる。後者の場合、コンパイラは関数が引数なしで呼び出されたかを検査しないためである [TIGCC, void usage]。それゆえ、任意の引数を指定して呼び出された関数は、コンパイル時に警告なしで受け付けられる。
void
引数を宣言しないと、次の結果を引き起こすことがある。
- 呼び出し側と呼び出し先の間の関数のインターフェイスがあいまいになる
- 機密情報の漏えい
類似のレコメンデーション、「DCL07-C. 関数宣言子には適切な型情報を含める」では、より一般的な意味合いで引数の型を取り上げている。
違反コード (あいまいなインターフェイス)
以下のコード例では、呼び出し側は引数 3 を指定して foo()
を呼び出している。呼び出し側は、foo()
は int
型の引数を 1 つだけ受け付け、長いメッセージの一部としてこの引数を出力するものと想定している。foo()
は void
引数なしで宣言されているため、コンパイラは呼び出し側の検査をまったく行わない。そのため、呼び出し側は誤りを検出できない可能性がある。たとえばこの例では、foo()
は値 3 を期待通りに出力する可能性がある。
関数の引数がないということは、任意の引数を指定できるという意味になるため、呼び出し側は任意の数の引数を関数に指定できる。
/* foo.h において */ void foo(); /* foo.c において */ void foo() { int i = 3; printf("i value: %d\n", i); } /* caller.c において */ #include "foo.h" foo(3);
適合コード(あいまいなインターフェイス)
以下の適合コードでは、foo
の関数原型の宣言において、void
が明示的に引数として指定されている。
/* foo.h において */ void foo(void); /* foo.c において */ void foo(void) { int i = 3; printf("i value: %d\n", i); } /* caller.c において */ #include "foo.h" foo(3);
処理系固有の詳細 (あいまいなインターフェイス)
この適合コードを使用して foo(3)
の呼び出しが行われた場合、GCC コンパイラは次の診断メッセージを発行し、プログラマに対して関数インタフェースの誤用について警告する。
error: too many arguments to function 'foo'
違反コード (情報漏えい)
生じる可能性のある別の脆弱性として、機密情報の漏えいがある。以下のコード例では、高い権限をもつユーザが何らかの機密情報を呼び出し側に入力として渡し、呼び出し側はそれを foo()
に渡している。foo()
の定義方法が原因で、読者は、foo()
には呼び出し側から情報を取得する手段はないと想定してしまうかもしれない。しかし、i
の値は実際には(呼び出し側の戻り先アドレスよりも前の)スタックに渡されるため、悪意あるプログラマが内部の実装を書き換えて、より低い権限のファイルにこの値を手動でコピーする可能性がある。
/* コンパイルには gcc4.3.3 を使用 */ void foo() { /* asm コードを使用して i を暗黙的に * 呼び出し側から取得し、 * より権限の低いファイルにこれを渡す */ } ... /* 呼び出し側 */ foo(i); /* i はユーザからの入力により渡される */
適合コード(情報漏えい)
void foo(void) { int i = 3; printf("i value: %d\n", i); }
ここでも、最も簡単な適合コードは、唯一の引数として void
を明示的に指定することである。
リスク評価
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
DCL20-C |
中 |
中 |
低 |
P12 |
L1 |
関連するガイドライン
C++ では、foo()
と foo(void)
は意味も効果もまったく同じであるため、このルールは C++ には適用されない。しかし、foo()
の代わりに foo(void)
を明示的に宣言して、任意の数の任意の型の引数を受け付ける foo(...)
と区別するべきである。
参考資料
Section 6.11.6, "Function Declarators"
翻訳元
これは以下のページを翻訳したものです。
DCL20-C. Always specify void even if a function accepts no arguments (revision 50)