GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Was ist :-!! im C-Code?

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

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


Dies ist im Endeffekt 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 wie BUILD_BUG_OR_ZERO sein , statt ...ON_ZERO . (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 den Ausdruck e .

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

  3. -!!(e) :Den Ausdruck aus Schritt 2 numerisch negieren:0 wenn es 0 wäre; 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 nicht ist Null, dann wird es eine negative Zahl sein. Beliebiges Bitfeld mit negativ deklarieren 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 einen size_t mit der entsprechenden Breite (die Null sein wird, wenn e ist Null).

Einige Leute haben gefragt:Warum nicht einfach einen 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 keine Probleme in Ihrem Kernel entdecken 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.


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 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. Das ist der Grund, warum Kernel-Entwickler damit beginnen, Tricks wie Bitfeldbreiten mit negativer Größe und Arrays mit negativer Größe zu verwenden (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 den error eingeführt Funktionsattribut, mit dem Sie dieses ältere Konzept erweitern können, aber einen Kompilierzeitfehler mit einer Nachricht Ihrer Wahl erzeugen - keine kryptischen "Arrays mit negativer Größe"-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 die diese Funktion und die meisten Makros in bug.h verwendet wurden entsprechend aktualisiert. Dennoch kann dieses Makro nicht als Initialisierer verwendet werden. Verwenden Sie jedoch by Anweisungsausdrücke (eine weitere GCC-C-Erweiterung), das 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 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 Kompilierungskonstante 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 (d. h. vollständig ausgewertet werden kann zur Kompilierzeit und übergibt andernfalls den __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.


Die : ist ein Bitfeld. Wie bei !! , das ist eine logische doppelte Negation und gibt daher 0 zurück für false oder 1 für wahr. Und die - ist ein Minuszeichen, also arithmetische Negation.

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

Betrachten Sie BUILD_BUG_ON_ZERO . Wenn -!!(e) ergibt einen negativen Wert, der einen Kompilierfehler erzeugt. Sonst -!!(e) wird zu 0 ausgewertet, und ein Bitfeld mit einer Breite von 0 hat die Größe 0. Und daher wird das Makro zu size_t ausgewertet mit Wert 0.

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

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


Linux
  1. Was sind Bash-Exit-Codes in Linux

  2. Woher weiß ich, was die „Errno“ bedeutet?

  3. Was bedeutet es zu sagen, dass der Linux-Kernel präventiv ist?

  4. Welchen Fehlercode gibt ein Prozess zurück, der Segfaults zurückgibt?

  5. Was bedeutet, wenn [[ $? -ne 0 ]]; bedeuten in .ksh

403 Verbotener Fehler – Was ist das und wie kann man ihn beheben?

Was ist der Fehler 503 Dienst nicht verfügbar?

Was ist ein 500 Internal Server Error?

Was ist ein 503-Fehler „Dienst nicht verfügbar“?

Linux-VPN-Plug-in-Fehler – Was nun?

Was ist Python:Eine Einführung in eine plattformübergreifende Programmiersprache