it-swarm.com.de

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

Ich bin in /usr/include/linux/kernel.h auf diesen seltsamen Makrocode gestoßen:

/* 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 :-!!?

1604
chmurli

Dies ist im Endeffekt eine Möglichkeit, um zu überprüfen, ob der Ausdruck e als 0 ausgewertet werden kann, und falls nicht, um den Build fehlzuschlagen .

Das Makro ist etwas falsch benannt. es sollte eher etwas wie BUILD_BUG_OR_ZERO als ...ON_ZERO sein. (Es gab gelegentliche Diskussionen darüber, ob dies ein verwirrender Name ist.)

Sie sollten den Ausdruck so lesen:

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

  2. !!(e): Zweimal logisch negieren: 0 if e == 0; sonst 1.

  3. -!!(e): Negieren Sie den Ausdruck aus Schritt 2 numerisch: 0, wenn es 0 war; sonst -1.

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

  5. struct{int: -!!(1);} --> struct{int: -1;}: Wenn es andererseits nicht Null ist, wird es eine negative Zahl sein. Das Deklarieren eines Bitfelds mit negativer Breite ist ein Kompilierungsfehler.

Also werden wir entweder ein Bitfeld mit der Breite 0 in einer Struktur haben, was in Ordnung ist, oder ein Bitfeld mit negativer Breite, was ein Kompilierungsfehler ist. Dann nehmen wir sizeof dieses Feld, so dass wir einen size_t mit der entsprechenden Breite erhalten (die Null sein wird, wenn e Null ist).


Einige Leute haben gefragt: Warum nicht einfach ein assert verwenden?

Keithmos Antwort hier hat eine gute Antwort:

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

Genau richtig. Sie möchten zur Laufzeit keine Probleme in Ihrem Kernel entdecken, die früher hätten erkannt werden können! Es ist ein kritisches Stück des Betriebssystems. Inwieweit Probleme während der Kompilierung erkannt werden können, ist umso besser.

1635
John Feminella

Der : ist ein Bitfeld. Was !! betrifft, so ist das logische doppelte Negation und gibt 0 für falsch oder 1 für wahr zurück. Und der - ist ein Minuszeichen, d. H. Eine arithmetische Negation.

Es ist alles nur ein Trick, um den Compiler dazu zu bringen, ungültige Eingaben zu ignorieren.

Betrachte BUILD_BUG_ON_ZERO. Wenn -!!(e) einen negativen Wert ergibt, wird ein Kompilierungsfehler erzeugt. Andernfalls wird -!!(e) zu 0 ausgewertet, und ein Bitfeld mit der Breite 0 hat die Größe 0. Daher wird das Makro zu size_t mit dem Wert 0 ausgewertet.

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

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

250
David Heffernan

Einige Leute scheinen diese Makros mit assert() zu verwechseln.

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

160
keithmo

Nun, ich bin ziemlich überrascht, dass die Alternativen zu dieser Syntax nicht erwähnt wurden. Ein weiterer üblicher (aber älterer) Mechanismus besteht darin, eine nicht definierte Funktion aufzurufen und den Funktionsaufruf mithilfe des Optimierers zu kompilieren, wenn Ihre Behauptung 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 erst dann ein Fehler gemeldet wird, wenn Sie eine Verknüpfung herstellen. Zu diesem Zeitpunkt kann die Definition für die Funktion you_did_something_bad () nicht gefunden werden. Das ist der Grund, warum Kernel-Entwickler damit beginnen, Tricks wie die negativ bemessenen Bitfeldbreiten und die negativ bemessenen Arrays zu verwenden (von denen letztere in GCC 4.4 aufgehört haben, Builds zu brechen).

Aus Sympathie für die Notwendigkeit von Behauptungen zur Kompilierungszeit hat GCC 4.3 das Funktionsattribut error eingeführt, mit dem Sie dieses ältere Konzept erweitern, aber mit einer Meldung Ihrer Wahl einen Fehler zur Kompilierungszeit generieren können. - Keine kryptischen "Negative Size Array" -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 seit Linux 3.9 ein Makro namens compiletime_assert , das diese Funktion verwendet, und die meisten Makros in bug.h waren entsprechend aktualisiert. Dieses Makro kann jedoch nicht als Initialisierungsprogramm verwendet werden. Wenn Sie jedoch die Anweisungsausdrücke (eine andere GCC-C-Erweiterung) verwenden, 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 wertet seinen Parameter genau einmal aus (falls es Nebenwirkungen hat) und erstellt beim Kompilieren einen Fehler, der besagt: "Ich habe dir gesagt, dass du mir keine Fünf geben sollst!" Wenn der Ausdruck fünf ergibt oder keine Kompilierzeitkonstante ist.

Warum verwenden wir das nicht anstelle von negativ großen Bitfeldern? Leider gibt es derzeit viele Einschränkungen bei der Verwendung von Anweisungsausdrücken, einschließlich ihrer Verwendung als konstante Initialisierer (für Aufzählungskonstanten, Bitfeldbreite usw.), selbst wenn der Anweisungsausdruck selbst vollständig konstant ist (dh vollständig ausgewertet werden kann) zur Kompilierungszeit und besteht andernfalls den Test __builtin_constant_p() ). Darüber hinaus können sie nicht außerhalb eines Funktionskörpers verwendet werden.

Hoffentlich wird GCC diese Mängel bald ändern und die Verwendung konstanter Anweisungsausdrücke als konstante Initialisierer ermöglichen. Die Herausforderung hierbei ist die Sprachspezifikation, die definiert, was ein legaler konstanter Ausdruck ist. C++ 11 hat das Schlüsselwort constexpr nur für diesen Typ oder diese Sache hinzugefügt, aber in C11 gibt es kein Gegenstück. Während C11 statische Behauptungen erhalten hat, die einen Teil dieses Problems lösen, werden nicht alle diese Mängel behoben. Daher hoffe ich, dass gcc eine constexpr-Funktionalität als Erweiterung über -std = gnuc99 & -std = gnuc11 oder eine solche zur Verfügung stellen kann und die Verwendung für Anweisungsausdrücke und dergleichen zulässt. al.

48
Daniel Santos

Es wird ein Bitfeld der Größe 0 erstellt, wenn die Bedingung falsch ist, aber ein Bitfeld der Größe -1 (-!!1), wenn die Bedingung wahr/ungleich Null ist. Im ersten Fall liegt kein Fehler vor und die Struktur wird mit einem int-Member initialisiert. In letzterem Fall liegt ein Kompilierungsfehler vor (und natürlich wird kein Bitfeld der Größe -1 erstellt).

34
Matt Phillips