私は / usr/include/linux/kernel.h でこの奇妙なマクロコードにぶつかった。
/* Force a compilation error if condition is true, but also produce a
result (of value 0 and type size_t), so the expression can be used
e.g. in a structure initializer (or where-ever else comma expressions
aren't permitted). */
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
:-!!
は何をしますか?
これは、事実上、式eが0であると評価できるかどうかをチェックし、そうでない場合はbuildを失敗させる方法です。
このマクロの名前はやや間違っています。 BUILD_BUG_OR_ZERO
ではなく...ON_ZERO
のようなものにするべきです。 (これが混乱する名前であるかどうかについての時折の議論がありました。)
あなたはこのような表現を読むべきです:
sizeof(struct { int: -!!(e); }))
(e)
:式e
を計算します。
!!(e)
:論理的に2回否定する:0
if e == 0
;それ以外の場合は1
。
-!!(e)
:ステップ2の式を数値的に否定します。0
が0
の場合それ以外の場合は-1
。
struct{int: -!!(0);} --> struct{int: 0;}
:それがゼロだった場合、幅ゼロの無名整数ビットフィールドを持つ構造体を宣言します。すべて問題ありませんが、通常どおりに作業を進めます。
struct{int: -!!(1);} --> struct{int: -1;}
:一方、がゼロでない場合、負の数になります。 負幅のビットフィールドを宣言すると、コンパイルエラーになります。
そのため、構造体の幅が0のビットフィールド(細かい値)、または負の幅のビットフィールド(コンパイルエラー)のいずれかを使用します。それからsizeof
そのフィールドを取るので、適切な幅のsize_t
を取得します(e
がゼロの場合はゼロになります)。
なぜassert
を使わないのですか?
keithmo's answer ここに良い返答があります:
これらのマクロはコンパイル時テストを実装していますが、assert()は実行時テストです。
その通りです。実行時にカーネルの中で以前に検出された可能性がある問題を検出したくはありません。それはオペレーティングシステムの重要な部分です。コンパイル時に問題をどの程度まで検出できるかは、それほど良くありません。
:
はビットフィールドです。 !!
については、 論理二重否定 であるため、falseの場合は0
、trueの場合は1
を返します。そして-
はマイナス記号、すなわち算術否定です。
無効な入力をコンパイラに認識させるのは、単なるトリックです。
BUILD_BUG_ON_ZERO
を考えます。 -!!(e)
が負の値に評価されると、コンパイルエラーが発生します。それ以外の場合、-!!(e)
は0と評価され、0幅のビットフィールドのサイズは0になります。したがって、マクロは0の値を持つsize_t
として評価されます。
入力がnotzeroである場合、実際にはビルドが失敗するため、この名前は私の考えでは弱くなっています。
BUILD_BUG_ON_NULL
はよく似ていますが、int
ではなくポインタを生成します。
一部の人々はこれらのマクロをassert()
と混同しているようです。
これらのマクロはコンパイル時テストを実装していますが、assert()
はランタイムテストです。
まあ、私はこの構文の代替が言及されていないことを非常に驚いています。もう1つの一般的な(ただし古い)メカニズムは、定義されていない関数を呼び出し、アサーションが正しい場合はオプティマイザに依存してその関数呼び出しをコンパイルすることです。
#define MY_COMPILETIME_ASSERT(test) \
do { \
extern void you_did_something_bad(void); \
if (!(test)) \
you_did_something_bad(void); \
} while (0)
このメカニズムは機能しますが(最適化が有効になっている限り)、リンクするまでエラーを報告しないという欠点があります。その時点では、関数you_did_something_bad()の定義が見つかりません。そのため、カーネル開発者が負のサイズのビットフィールド幅や負のサイズの配列(後者はGCC 4.4のビルドを壊すのをやめました)のようなトリックを使い始めました。
コンパイル時アサーションが必要であることを念頭に置いて、GCC 4.3ではこの古い概念を拡張することができますが、以下のメッセージでコンパイル時エラーを生成することができる error
関数属性 が導入されました。あなたの選択 - これ以上不可解な "負のサイズの配列"エラーメッセージはありません!
#define MAKE_SURE_THIS_IS_FIVE(number) \
do { \
extern void this_isnt_five(void) __attribute__((error( \
"I asked for five and you gave me " #number))); \
if ((number) != 5) \
this_isnt_five(); \
} while (0)
実際、Linux 3.9では、この機能を使用する compiletime_assert
というマクロがあり、 bug.h
のほとんどのマクロはそれに応じて更新されています。 。それでも、このマクロは初期化子としては使用できません。ただし、by ステートメント式 (別のGCC C拡張)を使用すると、可能です。
#define ANY_NUMBER_BUT_FIVE(number) \
({ \
typeof(number) n = (number); \
extern void this_number_is_five(void) __attribute__(( \
error("I told you not to give me a five!"))); \
if (n == 5) \
this_number_is_five(); \
n; \
})
このマクロはそのパラメータを一度だけ評価し(副作用がある場合)、 "私はあなたに5を与えないように言った!"というコンパイル時エラーを作ります。式が5に評価されるか、コンパイル時定数ではない場合.
それでは、なぜ負のサイズのビットフィールドの代わりにこれを使用しないのでしょうか。残念ながら、文の式が完全に定数であっても(つまり、完全に評価できる場合でも)定数初期化子としての使用(enum定数、ビットフィールド幅など)を含め、文式の使用には現在多くの制限があります。コンパイル時に、そうでなければ __builtin_constant_p()
テストに合格します。また、関数本体の外部では使用できません。
うまくいけば、GCCはこれらの欠点をすぐに修正し、定数ステートメント式を定数初期化子として使用できるようにするでしょう。ここでの課題は、正当な定数式とは何かを定義する言語仕様です。 C++ 11では、この型またはものだけにconstexprキーワードが追加されましたが、C11にはこれに相当するものはありません。 C11はこの問題の一部を解決する静的なアサーションを取得しましたが、これらの欠点のすべてを解決することはできません。ですから、gccがconstexpr機能を-std = gnuc99&-std = gnuc11などの拡張として利用できるようにして、ステートメント式などで使用できるようにしたいと思っています。 al。
条件が偽の場合はサイズ0
ビットフィールドを作成しますが、条件が真またはゼロ以外の場合はサイズ-1
(-!!1
)ビットフィールドを作成します。前者の場合、エラーは発生せず、構造体はintメンバーで初期化されます。後者の場合、コンパイルエラーが発生します(もちろん、サイズ-1
ビットフィールドなどは作成されません)。