これらのマクロを assert()
と混同している人がいるようです .
これらのマクロはコンパイル時テストを実装しますが、assert()
実行時テストです。
これは事実上、式 e を 0 に評価できるかどうかをチェックし、そうでない場合はビルドを失敗させる方法です .
マクロの名前が少し間違っています。 BUILD_BUG_OR_ZERO
のようなものにする必要があります ...ON_ZERO
ではなく . (これが紛らわしい名前かどうかについて、時折議論がありました。 .)
この式は次のように読む必要があります:
sizeof(struct { int: -!!(e); }))
<オール>
(e)
:式 e
を計算します .
!!(e)
:2 回論理否定:0
e == 0
の場合;それ以外の場合 1
.
-!!(e)
:ステップ 2 の式を数値的に否定します:0
0
だったら;それ以外の場合は -1
.
struct{int: -!!(0);} --> struct{int: 0;}
:ゼロの場合、幅ゼロの無名整数ビットフィールドを持つ構造体を宣言します。すべて問題なく、通常どおり進めます。
struct{int: -!!(1);} --> struct{int: -1;}
:一方、そうでない場合 ゼロの場合、それは負の数になります。 負でビットフィールドを宣言する width はコンパイル エラーです。
したがって、構造体の幅が 0 のビットフィールド (これは問題ありません)、または負の幅のビットフィールド (コンパイル エラー) になります。次に sizeof
を取ります そのフィールドなので、size_t
を取得します 適切な幅 (e
の場合はゼロ) はゼロです)。
なぜ assert
を使わないのかと質問する人もいます:?
ここでのkeithmoの回答には良い反応があります:
<ブロック引用>これらのマクロはコンパイル時テストを実装しますが、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; \
})
このマクロは、そのパラメーターを 1 回だけ評価し (副作用がある場合)、「5 を与えないように言った!」というコンパイル時エラーを作成します。式が 5 に評価されるか、コンパイル時の定数ではない場合。
では、負のサイズのビットフィールドの代わりにこれを使用しないのはなぜでしょうか?悲しいかな、現在、ステートメント式が完全に定数である (つまり、完全に評価できる) 場合でも、ステートメント式の使用には多くの制限があります。コンパイル時に、それ以外の場合は __builtin_constant_p()
を渡します テスト)。さらに、関数本体の外では使用できません。
願わくば、GCC がこれらの欠点をすぐに修正し、定数ステートメント式を定数初期化子として使用できるようにすることを願っています。ここでの課題は、正当な定数式とは何かを定義する言語仕様です。 C++11 では、この型またはものだけに constexpr キーワードが追加されましたが、C11 には対応するものはありません。 C11 は、この問題の一部を解決する静的アサーションを取得しましたが、これらの欠点のすべてを解決するわけではありません。したがって、gcc が -std=gnuc99 &-std=gnuc11 などを介して constexpr 機能を拡張機能として利用できるようにし、ステートメント式 et での使用を許可できることを願っています。
:
ビットフィールドです。 !!
について 、これは論理二重否定であるため、0
を返します。 false または 1
の場合 本当です。そして -
はマイナス記号、つまり算術否定です。
無効な入力に対してコンパイラに barf をさせるのは、すべて単なるトリックです。
BUILD_BUG_ON_ZERO
を検討してください . -!!(e)
の場合 コンパイル エラーを生成する負の値に評価されます。それ以外の場合 -!!(e)
は 0 に評価され、幅 0 のビットフィールドのサイズは 0 になります。したがって、マクロは size_t
に評価されます。 値は 0 です。
入力が not の場合、ビルドは実際には失敗するため、私の見解では名前は弱いです。 ゼロ。
BUILD_BUG_ON_NULL
非常に似ていますが、int
ではなくポインターを生成します .