Frage Was ist ": - !!" in C-Code?


Ich stieß auf diesen merkwürdigen Makrocode herein /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); }))

Was macht :-!! machen?


1476
2018-02-10 14:50


Ursprung


Antworten:


Dies ist in der Tat eine Möglichkeit, um zu überprüfen, ob der Ausdruck e zu 0 ausgewertet werden kann und wenn nicht, um den Build zu beenden.

Das Makro ist etwas falsch benannt; es sollte etwas mehr wie sein BUILD_BUG_OR_ZERO, eher, als ...ON_ZERO. (Da waren gelegentliche Diskussionen darüber, ob das ein verwirrender Name ist.)

Du solltest den Ausdruck so lesen:

sizeof(struct { int: -!!(e); }))
  1. (e): Ausdruck berechnen e.

  2. !!(e): Logisch zweimal negieren: 0 ob e == 0; Andernfalls 1.

  3. -!!(e): Negieren Sie den Ausdruck von Schritt 2: 0 wenn es war 0; Andernfalls -1.

  4. struct{int: -!!(0);} --> struct{int: 0;}: Wenn es Null war, dann deklarieren wir eine Struktur mit einem anonymen Integer-Bitfeld mit der Breite Null. Alles ist in Ordnung und wir gehen wie gewohnt vor.

  5. struct{int: -!!(1);} --> struct{int: -1;}: Auf der anderen Seite, wenn es ist nicht Null, dann wird es eine negative Zahl sein. Jedes Bitfeld mit deklarieren Negativ width ist ein Übersetzungsfehler.

Also werden wir entweder mit einem Bitfeld mit der Breite 0 in einer Struktur enden, was in Ordnung ist, oder mit einem Bitfeld mit negativer Breite, was ein Kompilierungsfehler ist. Dann nehmen wir sizeof dieses Feld, also bekommen wir ein size_t mit der entsprechenden Breite (die für den Fall, dass ... e ist Null).


Einige Leute haben gefragt: Warum nicht einfach ein assert?

keithmos Antwort Hier hat eine gute Antwort:

Diese Makros implementieren einen Kompilierungszeittest, während assert () ein Laufzeittest ist.

Genau richtig. Sie möchten keine Probleme in Ihrem System erkennen Kernel zur Laufzeit hätte das früher erwischt werden können! Es ist ein kritischer Teil des Betriebssystems. In welchem ​​Umfang auch immer Probleme zur Kompilierzeit erkannt werden können, umso besser.


1530
2018-02-10 15:04



Das : ist ein Bitfeld. Wie für !!, das ist logische doppelte Verneinung und kehrt zurück 0 für falsch oder 1 für wahr. Und das - ist ein Minuszeichen, d. h. arithmetische Negation.

Es ist alles nur ein Trick, um den Compiler bei ungültigen Eingaben zu blockieren.

Erwägen BUILD_BUG_ON_ZERO. Wann -!!(e) wird zu einem negativen Wert ausgewertet, der einen Kompilierfehler verursacht. Andernfalls -!!(e) wird zu 0 ausgewertet, und ein Bitfeld mit der Breite 0 hat die Größe 0. Daher wird das Makro zu a ausgewertet size_t mit Wert 0.

Der Name ist meiner Ansicht nach schwach, weil der Build tatsächlich fehlschlägt, wenn die Eingabe erfolgt nicht Null.

BUILD_BUG_ON_NULL ist sehr ähnlich, ergibt aber eher einen Zeiger als einen int.


235
2018-02-10 14:54



Manche Leute scheinen diese Makros zu verwirren assert().

Diese Makros implementieren einen Test zur Kompilierungszeit, während assert() ist ein Laufzeittest.


149
2018-02-10 15:37



Nun, ich bin ziemlich überrascht, dass die Alternativen zu dieser Syntax nicht erwähnt wurden. Ein weiterer häufiger (aber älterer) Mechanismus besteht darin, eine Funktion aufzurufen, die nicht definiert ist, und sich darauf zu verlassen, dass der Optimierer den Funktionsaufruf kompiliert, wenn Ihre Assertion korrekt ist.

#define MY_COMPILETIME_ASSERT(test)              \
    do {                                         \
        extern void you_did_something_bad(void); \
        if (!(test))                             \
            you_did_something_bad(void);         \
    } while (0)

Während dieser Mechanismus funktioniert (solange Optimierungen aktiviert sind), hat er den Nachteil, dass er erst dann einen Fehler meldet, wenn Sie ihn verlinken. Zu diesem Zeitpunkt findet er die Definition für die Funktion you_did_something_bad () nicht. Das ist der Grund, warum Kernel-Entwickler Tricks wie die Bitfeldbreiten der negativen Größe und die Arrays negativer Größe (die später in GCC 4.4 die Builds gestoppt haben) verwenden.

Aus Sympathie für die Notwendigkeit kompilierbarer Behauptungen führte GCC 4.3 das error Funktionsattribut das erlaubt Ihnen, dieses ältere Konzept zu erweitern, aber einen Fehler bei der Kompilierung mit einer Nachricht Ihrer Wahl zu erzeugen - keine kryptischen "Negativgrößen" -Fehlermeldungen mehr!

#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)

Tatsächlich haben wir ab Linux 3.9 jetzt ein Makro namens compiletime_assert die diese Funktion und die meisten Makros verwendet bug.h wurden entsprechend aktualisiert. Dennoch kann dieses Makro nicht als Initialisierer verwendet werden. Verwendung von Anweisungsausdrücke (eine weitere GCC C-Erweiterung), können Sie!

#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;                                                    \
    })

Dieses Makro wird seinen Parameter genau einmal auswerten (falls es Nebenwirkungen hat) und einen Kompilierungsfehler erstellen, der besagt "Ich habe dir gesagt, dass du mir keine fünf gibst!" wenn der Ausdruck fünf ergibt oder keine Kompilierzeitkonstante ist.

Warum verwenden wir das nicht anstelle von Bitfeldern mit negativer Größe? Leider gibt es derzeit viele Einschränkungen bei der Verwendung von Anweisungsausdrücken, einschließlich ihrer Verwendung als Konstanteninitialisierer (für Enum-Konstanten, Bitfeldbreite usw.), selbst wenn der Anweisungsausdruck vollständig selbst konstant ist (dh vollständig ausgewertet werden kann) zur Kompilierzeit und übergibt sonst die __builtin_constant_p() Prüfung). Außerdem können sie nicht außerhalb eines Funktionskörpers verwendet werden.

Hoffentlich wird der GCC diese Mängel bald korrigieren und ermöglichen, dass konstante Anweisungsausdrücke als konstante Initialisierer verwendet werden. Die Herausforderung hier ist die Sprachspezifikation, die definiert, was ein gesetzlicher konstanter Ausdruck ist. C ++ 11 hat das Schlüsselwort constexpr nur für diesen Typ oder dieses Ding hinzugefügt, aber in C11 ist kein Gegenstück vorhanden. Während C11 statische Behauptungen erhielt, die einen Teil dieses Problems lösen werden, wird es nicht alle diese Mängel lösen. Also hoffe ich, dass gcc eine conexpr-Funktionalität als Erweiterung über -std = gnuc99 & -std = gnuc11 oder etwas ähnliches zur Verfügung stellen kann und seine Verwendung für Anweisungsausdrücke et. al.


42
2018-06-27 08:21



Es erstellt eine Größe 0 Bitfeld, wenn die Bedingung falsch ist, aber eine Größe -1 (-!!1) Bitfeld, wenn die Bedingung wahr / ungleich Null ist. Im ersten Fall gibt es keinen Fehler und die Struktur wird mit einem int-Member initialisiert. Im letzteren Fall gibt es einen Kompilierungsfehler (und keine Größe -1 Bitfeld ist natürlich erstellt).


31
2018-02-10 14:54



 Linux Kernel :   

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

-1
2018-06-21 07:18