とは ":-!!" Cコードで?


1665

/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); }))

何をし:-!!ますか?


2
-単項マイナス<br />!論理NOT <br />指定された整数eの逆ではないので、変数は0または1のいずれかになります
CyrillC

69
git blameは、この特定の形式の静的アサーションがJan Beulichによって8c87df4で導入されたことを示しています。明らかに、彼にはそれをする十分な理由がありました(コミットメッセージを参照)。
Niklas B.

55
@Lundin:assert()はコンパイル時エラーを引き起こしません。以上が上記の構造の要点です。
Chris Pacejo

4
@GreweKokkor Linuxは大きすぎて一人ですべてを処理することはできません。Linusには副官がいて、彼らには変更と改善をボトムアップで推進する彼らがいます。Linusは、機能が必要かどうかを決定するだけですが、同僚をある程度信頼しています。オープンソース環境での分散システムの動作について詳しく知りたい場合は、youtubeビデオを確認してください:youtube.com/watch ?v=4XpnKHJAok8(非常に興味深い話です)。
Tomas Pruzina

3
@cpcloud sizeofは、値ではなくタイプを「評価」します。この場合、その型は無効です。
Winston Ewert 2012年

回答:


1692

これは、実際には、式eが0であると評価できるかどうかをチェックし、そうでない場合はビルドを失敗させる方法です。

マクロの名前は多少間違っています。それはBUILD_BUG_OR_ZERO、というよりはむしろ似ているべきです...ON_ZERO。(これが紛らわしい名前であるかどうかについて、時々議論がありました。)

次のような式を読む必要があります。

sizeof(struct { int: -!!(e); }))
  1. (e):式を計算しますe

  2. !!(e):論理的に2回否定します:0if e == 0; それ以外の場合1

  3. -!!(e):数値は、ステップ2からの発現を否定:0それがあった場合0。それ以外の場合-1

  4. struct{int: -!!(0);} --> struct{int: 0;}:ゼロの場合は、幅がゼロの匿名整数ビットフィールドを持つ構造体を宣言します。すべて順調で、通常どおり続行します。

  5. struct{int: -!!(1);} --> struct{int: -1;}:一方、ゼロでない場合は、負の数になります。負の幅のビットフィールドを宣言すると、コンパイルエラーが発生します。

したがって、構造体の幅が0のビットフィールド(問題ありません)か、負の幅のビットフィールド(コンパイルエラー)のいずれかになります。次にsizeof、そのフィールドを取得しsize_tて、適切な幅(がゼロの場合eはゼロになります)を持つaを取得します。


一部の人々は尋ねました:なぜ単に使用しないのassertですか?

ここでのkeithmoの答えは良い応答です:

これらのマクロはコンパイル時テストを実装し、assert()はランタイムテストです。

その通りです。カーネルの実行時に、以前に検出された可能性のある問題を検出する必要はありません。これはオペレーティングシステムの重要な部分です。コンパイル時に問題を検出できる範囲で、かなり良いです。


5
@westonいろいろな場所がたくさん。自分で見て!
John Feminella、2012

166
C ++またはC標準の最近のバリアントにはstatic_assert、関連する目的のようなものがあります。
Basile Starynkevitch、2012

54
@Lundin-#errorは、3行のコード#if /#error /#endifの使用を必要とし、プリプロセッサがアクセスできる評価でのみ機能します。このハックは、コンパイラがアクセスできるすべての評価で機能します。
Ed Staub、2012

236
Linuxカーネルは、少なくともLinusがまだ生きている間はC ++を使用しません。
Mark Ransom、2012

6
@ Dolda2000:「Cのブール式は常に0または1に評価されるように定義されています」-正確ではありません。オペレータ収率「論理ブール」結果は(!<><=>===!=&&||)は常に0または1である他の式を得る条件として使用することができる結果をもたらす、単にゼロまたは非ゼロであることができます。例えばisdigit(c)、ここでcの数字であり、得ることができる任意の(その後の状態で真として扱われる)は、非ゼロ値を。
キース・トンプソン

256

これ:はビットフィールドです。については!!、これは論理的な二重否定であるため0、falseまたは1trueで返されます。そして、これ-はマイナス記号、つまり算術否定です。

無効な入力に対してコンパイラーにbarfを実行させるのは、単なるトリックです。

考えてくださいBUILD_BUG_ON_ZERO。場合は-!!(e)、コンパイルエラーが発生し、負の値、と評価されます。それ以外の場合-!!(e)は0と評価され、幅0のビットフィールドのサイズは0になりsize_tます。したがって、マクロは値0のa と評価されます。

入力がゼロでない場合、ビルドは実際に失敗するため、名前は私の見方では弱いです。

BUILD_BUG_ON_NULLは非常に似ていますが、ではなくポインタを生成しますint


14
されてsizeof(struct { int:0; })厳密に準拠しましたか?
ouah

7
なぜ一般的に結果は0どうなるのでしょうか?struct空のビットフィールドのみを持つA 、trueですが、サイズ0の構造体は許可されていないと思います。たとえば、そのタイプの配列を作成する場合でも、個々の配列要素は異なるアドレスを持つ必要がありますか?
Jens Gustedt、2012

2
彼らは実際にはGNU拡張機能を使用しているため気にしません。厳密なエイリアシングルールを無効にし、整数オーバーフローをUBと見なしません。しかし、これが厳密にCに準拠しているかどうか疑問に思っていました
ouah

3
@ouah無名の長さがゼロのビットフィールドについては、こちらを参照してください。stackoverflow.com/questions/4297095/...
デヴィッドHeffernanの

9
@DavidHeffernanは実際には0幅のないビットフィールドを許可しますが、構造内に他の名前付きメンバーがない場合は許可しません。(C99, 6.7.2.1p2) "If the struct-declaration-list contains no named members, the behavior is undefined."したがって、たとえばsizeof (struct {int a:1; int:0;})、厳密に準拠していますが、準拠していsizeof(struct { int:0; })ません(未定義の動作)。
ouah 2012年

168

一部の人々はこれらのマクロをと混同しているようですassert()

これらのマクロはコンパイル時テストを実装しますassert()が、ランタイムテストです。


52

さて、私はこの構文の代替が言及されていないことにかなり驚いています。もう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がconstexpr機能を-std = gnuc99&-std = gnuc11などの拡張機能として使用できるようにして、ステートメント式での使用を許可できることを願っています。al。


6
すべてのソリューションは代替手段ではありません。マクロの上のコメントはかなり明確です " so the expression can be used e.g. in a structure initializer (or where-ever else comma expressions aren't permitted)."マクロはタイプの式を返しますsize_t
Wiz

3
@Wizはい、私はこれを知っています。おそらくこれは少し冗長だったので、表現を再検討する必要があるかもしれませんが、私のポイントは、静的アサーションのさまざまなメカニズムを調査し、なぜ負のサイズのビットフィールドを使用しているのかを示すことでした。つまり、定数ステートメント式のメカニズムを取得すると、他のオプションが開かれます。
Daniel Santos

とにかく、これらのマクロを変数に使用することはできません。正しい?error: bit-field ‘<anonymous>’ width not an integer constant定数のみを許可します。それで、用途は何ですか?
Karthik Raj Palanichamy 2017年

1
@Karthik Linuxカーネルのソースを検索して、それが使用されている理由を確認してください。
Daniel Santos

@supercatコメントの関連性がまったくわかりません。あなたはそれを修正してください、あなたが何を意味するのかをよりよく説明するか、それを削除できますか?
Daniel Santos、

36

0条件が偽の場合はサイズビットフィールドが作成されますが、条件が真/非ゼロの場合はサイズ-1-!!1)ビットフィールドが作成されます。前者の場合、エラーはなく、構造体はintメンバーで初期化されます。後者の場合、コンパイルエラーが発生します(-1もちろん、サイズビットフィールドなどは作成されません)。


3
実際にsize_tは、条件がtrueの場合、値0のa を返します。
デビッドヘファーナン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.