Was ist “:-!!” im C-Code?

Lesezeit: 9 Minuten

Benutzeravatar von chmurli
chmurli

Ich bin auf diesen seltsamen Makrocode gestoßen /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 :-!! tun?

  • – Unäres Minus
    ! Logisches NOT
    inverses not not der gegebenen Ganzzahl e, also kann die Variable entweder 0 oder 1 sein.

    – KyrillC

    10. Februar 2012 um 14:58 Uhr

  • Git-Schuld sagt uns, dass diese besondere Form der statischen Behauptung war eingeführt von Jan Beulich in 8c87df4. Offensichtlich hatte er gute Gründe dafür (siehe Commit-Nachricht).

    – Niklas B.

    10. Februar 2012 um 16:34 Uhr


  • Es versteht sich fast von selbst, dass das erstellte Bitfeld ein anonymes ist. Dies ist im gleichen Sinne wie die C++-Template-Metaprogrammierung, dh Dinge passieren zur Kompilierzeit, die zur Kompilierzeit überprüft werden können.

    – Phorgan1

    16. Februar 2012 um 3:05 Uhr

  • Warten Sie, ich dachte, die Argumente von sizeof werden nicht ausgewertet. Ist das in diesem Fall falsch? Wenn ja warum? Weil es ein Makro ist?

    – Philipp Wolke

    11. März 2012 um 15:49 Uhr

  • @cpcloud, sizeof “wertet” den Typ aus, nur nicht den Wert. Sein Typ ist in diesem Fall ungültig.

    – Winston Ewert

    24. März 2012 um 17:19 Uhr

Benutzeravatar von John Feminella
Johannes Feminella

Dies ist in der Tat eine Möglichkeit zu prüfen, ob der Ausdruck e als 0 ausgewertet werden kann, und falls nicht, den Build fehlschlagen zu lassen.

Das Makro ist etwas falsch benannt; es sollte eher so etwas sein BUILD_BUG_OR_ZEROstatt ...ON_ZERO. (Da waren gelegentliche Diskussionen darüber, ob dies ein verwirrender Name ist.)

Sie sollten den Ausdruck so lesen:

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

  2. !!(e): Zweimal logisch negieren: 0 wenn e == 0; Andernfalls 1.

  3. -!!(e): Den Ausdruck aus Schritt 2 numerisch negieren: 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, das die Breite Null hat. Alles ist in Ordnung und wir fahren wie gewohnt fort.

  5. struct{int: -!!(1);} --> struct{int: -1;}: Andererseits, wenn es ist nicht Null, dann wird es eine negative Zahl sein. Deklarieren eines beliebigen Bitfelds mit Negativ Breite ist ein Kompilierungsfehler.

Wir erhalten also entweder ein Bitfeld mit der Breite 0 in einer Struktur, was in Ordnung ist, oder ein Bitfeld mit negativer Breite, was ein Kompilierungsfehler ist. Dann nehmen wir sizeof dieses Feld, also erhalten wir a size_t mit der entsprechenden Breite (die in dem Fall Null sein wird, wo e ist null).


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

keithmos Antwort hier hat eine gute Antwort:

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

Genau richtig. Sie möchten keine Probleme in Ihrem erkennen Kernel zur Laufzeit, die früher hätte abgefangen werden können! Es ist ein kritischer Teil des Betriebssystems. Inwieweit Probleme zur Kompilierzeit erkannt werden können, umso besser.

  • Neuere Varianten von C++- oder C-Standards haben so etwas wie static_assert für verwandte Zwecke.

    – Basile Starynkevitch

    10. Februar 2012 um 17:00 Uhr

  • @Lundin – #error würde die Verwendung von 3 Codezeilen #if/#error/#endif erfordern und würde nur für Auswertungen funktionieren, auf die der Präprozessor zugreifen kann. Dieser Hack funktioniert für jede Auswertung, auf die der Compiler zugreifen kann.

    – Ed Staub

    10. Februar 2012 um 17:50 Uhr

  • Der Linux-Kernel verwendet kein C++, zumindest nicht solange Linus noch lebt.

    – Markieren Sie Lösegeld

    10. Februar 2012 um 17:52 Uhr

  • @Dolda2000: “Boolesche Ausdrücke in C sind so definiert, dass sie immer zu Null oder Eins ausgewertet werden” — Nicht ganz. Die Betreiber die “logisch boolesche” Ergebnisse liefern (!, <, >, <=, >=, ==, !=, &&, ||) ergeben immer 0 oder 1. Andere Ausdrücke können Ergebnisse liefern, die als Bedingungen verwendet werden können, aber lediglich null oder ungleich null sind; zum Beispiel, isdigit(c)wo c eine Ziffer ist, nachgeben kann irgendein Wert ungleich Null (der dann in einer Bedingung als wahr behandelt wird).

    – Keith Thompson

    3. April 2013 um 3:23 Uhr

  • Kurzer Hinweis zum Namen. Es heißt ...ON_ZERO weil es ein Derivat von ist BUG_ONein Makro, das im Wesentlichen eine Behauptung ist. BUG_ON(foo) bedeutet “es ist ein Fehler, wenn foo ist wahr” (zur Laufzeit). Umgekehrt BUILD_BUG_ON ist eine statische Assertion (wird zur Build-Zeit überprüft) und schließlich BUILD_BUG_ON_ZERO ist genau dasselbe, außer dass das Ganze einem Ausdruck gleich ist (size_t)0wie der Kommentar in der Frage besagt.

    – Xion

    21. November 2016 um 23:37 Uhr

Benutzeravatar von David Heffernan
David Heffernan

Das : ist ein Bitfeld. Wie für !!das ist eine logische doppelte Negation und kehrt daher zurück 0 für falsch bzw 1 für wahr. Und die - ist ein Minuszeichen, dh arithmetische Negation.

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

In Betracht ziehen BUILD_BUG_ON_ZERO. Wann -!!(e) ergibt einen negativen Wert, der einen Kompilierfehler erzeugt. Andernfalls -!!(e) wird zu 0 ausgewertet, und ein Bitfeld mit einer Breite von 0 hat die Größe 0. Und daher wird das Makro zu a ausgewertet size_t mit Wert 0.

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

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

  • ist sizeof(struct { int:0; }) streng konform?

    – au

    10. Februar 2012 um 15:08 Uhr

  • Warum sollte das Ergebnis im Allgemeinen sein 0? EIN struct mit nur einem leeren Bitfeld, stimmt, aber ich glaube nicht, dass Structs mit der Größe 0 erlaubt sind. Wenn Sie zB ein Array dieses Typs erstellen würden, müssen die einzelnen Array-Elemente dennoch unterschiedliche Adressen haben, oder?

    – Jens Gustedt

    10. Februar 2012 um 15:09 Uhr

  • Es ist ihnen eigentlich egal, da sie GNU-Erweiterungen verwenden, die strikte Aliasing-Regel deaktivieren und Integer-Überläufe nicht als UB betrachten. Aber ich habe mich gefragt, ob dies streng konform zu C ist.

    – au

    10. Februar 2012 um 15:13 Uhr

  • @ouah bezüglich unbenannter Bitfelder der Länge Null, siehe hier: stackoverflow.com/questions/4297095/…

    – David Heffernan

    10. Februar 2012 um 15:14 Uhr


  • @DavidHeffernan tatsächlich erlaubt C unbenanntes Bitfeld von 0 Breite, aber nicht, wenn es kein anderes benanntes Mitglied in der Struktur gibt. (C99, 6.7.2.1p2) "If the struct-declaration-list contains no named members, the behavior is undefined." Also zum Beispiel sizeof (struct {int a:1; int:0;}) ist streng konform, aber sizeof(struct { int:0; }) ist nicht (undefiniertes Verhalten).

    – au

    16. Oktober 2012 um 13:51 Uhr

Benutzeravatar von keithmo
Keithmo

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

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

Benutzeravatar von Daniel Santos
Daniel Santos

Nun, ich bin ziemlich überrascht, dass die Alternativen zu dieser Syntax nicht erwähnt wurden. Ein weiterer gängiger (aber älterer) Mechanismus besteht darin, eine nicht definierte Funktion aufzurufen und sich darauf zu verlassen, dass der Optimierer den Funktionsaufruf kompiliert, 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)

Obwohl dieser Mechanismus funktioniert (solange Optimierungen aktiviert sind), hat er den Nachteil, dass kein Fehler gemeldet wird, bis Sie verlinken, und zu diesem Zeitpunkt kann er die Definition für die Funktion you_did_something_bad() nicht finden. Aus diesem Grund beginnen Kernel-Entwickler mit Tricks wie Bitfeldbreiten mit negativer Größe und Arrays mit negativer Größe (die letzteren haben in GCC 4.4 aufgehört, Builds zu beschädigen).

Aus Sympathie für die Notwendigkeit von Behauptungen zur Kompilierzeit hat GCC 4.3 die error Funktionsattribut Dadurch können Sie dieses ältere Konzept erweitern, aber einen Kompilierzeitfehler mit einer Nachricht Ihrer Wahl generieren – keine kryptischen Fehlermeldungen “Arrays mit negativer Größe” 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 darin verwendet bug.h wurden entsprechend aktualisiert. Dennoch kann dieses Makro nicht als Initialisierer verwendet werden. Verwenden Sie jedoch by Anweisungsausdrücke (eine weitere GCC-C-Erweiterung), Sie können!

#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 Nebeneffekte hat) und erzeugt einen Kompilierungsfehler, der besagt: “Ich habe dir gesagt, dass du mir keine Fünf geben sollst!” wenn der Ausdruck zu fünf ausgewertet wird 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 Aufzählungskonstanten, Bitfeldbreite usw.), selbst wenn der Anweisungsausdruck selbst vollständig konstant ist (dh vollständig ausgewertet werden kann). zur Kompilierzeit und übergibt ansonsten die __builtin_constant_p() Prüfung). Außerdem können sie nicht außerhalb eines Funktionskörpers verwendet werden.

Hoffentlich wird GCC diese Mängel bald beheben und die Verwendung konstanter Anweisungsausdrücke als konstante Initialisierer zulassen. Die Herausforderung hier ist die Sprachspezifikation, die definiert, was ein legaler konstanter Ausdruck ist. C++11 hat das Schlüsselwort constexpr nur für diesen Typ oder dieses Ding hinzugefügt, aber in C11 existiert kein Gegenstück. C11 hat zwar statische Zusicherungen erhalten, die einen Teil dieses Problems lösen, aber nicht alle diese Mängel. Ich hoffe also, dass gcc eine constexpr-Funktionalität als Erweiterung über -std=gnuc99 & -std=gnuc11 oder ähnliches verfügbar machen und ihre Verwendung für Anweisungsausdrücke et zulassen kann. Al.

Benutzeravatar von Matt Phillips
Matt Phillips

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

  • Eigentlich gibt es a zurück size_t mit dem Wert 0, falls die Bedingung wahr ist.

    – David Heffernan

    10. Februar 2012 um 14:58 Uhr

  • Eigentlich gibt es a zurück size_t mit dem Wert 0, falls die Bedingung wahr ist.

    – David Heffernan

    10. Februar 2012 um 14:58 Uhr

1428670cookie-checkWas ist “:-!!” im C-Code?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy