MSC38-C. マクロとして実装されている可能性のある定義済みの識別子をオブジェクトとして扱わない
C 標準、7.1.4節、第1パラグラフには次のように規定されている[ISO/IEC 9899:2011]。
あるヘッダ内で宣言する関数は、同じヘッダ内で定義する関数形式マクロとして追加して実装してもよい。ヘッダを取り込むことによってあるライブラリ関数を明示的に宣言する場合、その宣言が、そのヘッダ内で定義された関数形式マクロに影響されないことを保証するために、次に示す二つの方法のうちの一つを使うことができる。関数のマクロ定義を局所的に無効にするためには、(その名前の後ろに関数形式マクロの展開を指示する左括弧が続かなくなるので)括弧で関数名を囲めばよい。同じ構文上の理由で、あるライブラリ関数がマクロとしても定義されている場合でも、その関数のアドレスをとってもよい。185
185) これは処理系があるライブラリ関数をマクロで提供した場合でも、実際の関数を提供しなければならないことを意味する。
しかし、C 標準では、オブジェクトへのアクセスあるいは標準ライブラリのマクロ定義に展開される関数へのアクセスが未定義の動作となる例外を列挙している。そのようなマクロには、assert
、errno
、math_errhandling
、setjmp
、va_arg
、va_copy
、va_end
、va_start
が含まれる。これらのケースは未定義の動作 110, 114, 122, 124, 138 に記述されている。オブジェクトや関数にアクセスするためにこれらのマクロを無効にしてはならない。
違反コード(assert
)
以下の違反コード例では、標準 assert()
マクロを無効にし、execute_handler()
関数へのポインタとして渡そうとしている。assert()
の無効化は未定義の動作である。
#include <assert.h>
typedef void (*handler_type)(int);
void execute_handler(handler_type handler, int value) {
handler(value);
}
void func(int e) {
execute_handler(&(assert), e < 0);
}
適合コード(assert
)
この適合コードでは、assert()
マクロをヘルパー関数でラップすることで未定義の動作を取り除いている。
#include <assert.h>
typedef void (*handler_type)(int);
void execute_handler(handler_type handler, int value) {
handler(value);
}
static void assert_handler(int value) {
assert(value);
}
void func(int e) {
execute_handler(&assert_handler, e < 0);
}
違反コード(errno
の再定義)
次の違反コード例に示すように、レガシーコードは誤った宣言を含む傾向がある。
extern int errno;
適合コード(errno
の宣言)
errno
を正しく宣言する方法は、次の適合コードのように、ヘッダ <errno.h>
をインクルードすることである。
#include <errno.h>
C 標準に適合する処理系は、<errno.h>
の中で errno
を宣言することが要求されるが、歴史的にはこれを守らない実装も見られる。
リスク評価
このルールで取り上げたようなマクロの元になるオブジェクトや関数へのアクセスは未定義の動作となる。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
MSC38-C |
低 |
低 |
中 |
P2 |
L3 |
関連ガイドライン
CERT C コーディングスタンダード | DCL37-C. 予約済みの識別子を宣言または定義しない |
参考資料
ISO/IEC 9899:2011 | 7.1.4, "Use of Library Functions" |
翻訳元
これは以下のページを翻訳したものです。
MSC38-C. Do not treat a predefined identifier as an object if it might only be implemented as a macro (revision 39)