usr/include/linux/kernel.h]1の中で、この奇妙なマクロコードにぶつかりました。
/* 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と評価できるかどうかをチェックし、評価できない場合はビルドに失敗するということです。
このマクロの名前はやや間違っています。...ON_ZERO
ではなく、BUILD_BUG_OR_ZERO
のようにすべきです。(これは紛らわしい名前ではないかという時々の議論がありました)。
この表現はこのように読むべきです。
sizeof(struct { int: -!!(e); }))
1.(e)
:式 e
を計算します。
2.!!(e)
:e == 0なら
0、そうでなければ
1` というように、2回論理的に否定します。
3.-!!(e)
:ステップ2からの式を数値的に否定する: 0
であれば 0
、そうでなければ -1
。
4.4. struct{int: -!!(0);} --> struct{int: 0;}
:0だった場合は、幅が0の匿名の整数ビットフィールドを持つ構造体を宣言します。何も問題ないので、通常通りに進めます。
5.5. struct{int: -!!(1);} --> struct{int: -1;}
:一方、_0でない場合は、何らかの負の数になります。負の幅を持つビットフィールドを宣言するとコンパイルエラーになります。
つまり、構造体の中で幅が0のビットフィールドを宣言しても問題ないし、幅が負のビットフィールドを宣言してもコンパイルエラーになるということです。次に、そのフィールドの sizeof
を取り、適切な幅を持つ size_t
を得ます(e
がゼロの場合はゼロになります)。
ある人はこう尋ねました。**なぜ assert
を使用しないのですか?
こちらの keithmo's answer に良い回答があります。
これらのマクロはコンパイル時のテストを実装しているのに対し、 assert()は実行時のテストです。
まさにその通りです。もっと早くに発見できたかもしれないカーネルの問題を実行時に発見したくはありませんよね。カーネルは、オペレーティングシステムの重要な部分です。コンパイル時に問題を発見できるのであれば、それに越したことはありません。
:はビットフィールドです。!!!
については、論理的二重否定なので、偽の場合は0
、真の場合は1
を返します。また、-
はマイナス記号、つまり算術的否定です。
これはすべて、コンパイラが無効な入力に対して嘔吐するように仕向けるためのトリックなのです。
BUILD_BUG_ON_ZERO "を考えてみましょう。(e)-!!!(e)
が負の値に評価されると、コンパイルエラーになります。そうでなければ -!!(e)
は 0 と評価され、0 幅のビットフィールドは 0 のサイズを持ちます。
入力が 0 ではない場合、ビルドが実際に失敗するため、この名前は私の見解では弱いです。
BUILD_BUG_ON_NULLは非常に似ていますが、
int`ではなくポインタを返します。
これは、条件が偽の場合はサイズ 0
のビットフィールドを作成し、条件が真/ゼロでない場合はサイズ -1
(-!!1
) のビットフィールドを作成しています。 前者の場合,エラーは発生せず,構造体はintメンバで初期化されます。 後者の場合はコンパイルエラーになります(もちろん、サイズ -1
のビットフィールドなどは生成されません)。