Ich bin auf diesen seltsamen Makrocode in /usr/include/linux/kernel.h 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 :-!!
das
c
linux
macros
linux-kernel
chmurli
quelle
quelle
sizeof
"wertet" den Typ aus, nur nicht den Wert. Dies ist der Typ, der in diesem Fall ungültig ist.Antworten:
Auf diese Weise können Sie überprüfen, ob der Ausdruck e mit 0 bewertet werden kann, und wenn nicht, den Build fehlschlagen .
Das Makro ist etwas falsch benannt; es sollte eher so etwas sein
BUILD_BUG_OR_ZERO
als...ON_ZERO
. (Es gab gelegentlich Diskussionen darüber, ob dies ein verwirrender Name ist .)Sie sollten den Ausdruck folgendermaßen lesen:
(e)
: Ausdruck berechnene
.!!(e)
: Zweimal logisch negieren:0
ife == 0
; sonst1
.-!!(e)
: Negiere den Ausdruck aus Schritt 2 numerisch:0
wenn es war0
; sonst-1
.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.struct{int: -!!(1);} --> struct{int: -1;}
: Wenn es andererseits nicht Null ist, ist es eine negative Zahl. Das Deklarieren eines Bitfelds mit negativer Breite ist ein Kompilierungsfehler.Wir werden also 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, so dass wir einsize_t
mit der entsprechenden Breite erhalten (die Null ist, wenne
Null ist).Einige Leute haben gefragt: Warum nicht einfach eine verwenden
assert
?Keithmos Antwort hier hat eine gute Antwort:
Genau richtig. Sie möchten zur Laufzeit keine Probleme in Ihrem Kernel erkennen , die früher hätten erkannt werden können! Es ist ein kritischer Teil des Betriebssystems. Inwieweit Probleme beim Kompilieren erkannt werden können, umso besser.
quelle
static_assert
für verwandte Zwecke.!
,<
,>
,<=
,>=
,==
,!=
,&&
,||
) immer ergeben 0 oder 1. Andere Ausdrücke können Ergebnisse liefern , die als Bedingungen verwendet werden können, sondern nur Null oder ungleich Null; zum Beispielisdigit(c)
, woc
eine Ziffer ist, kann ergeben beliebigen Nicht-Null - Wert (die dann als wahr in einem Zustand , der behandelt wird).Das
:
ist ein Bitfeld. Das!!
ist eine logische doppelte Negation und gibt daher0
für falsch oder1
für wahr zurück. Und das-
ist ein Minuszeichen, dh eine arithmetische Negation.Es ist alles nur ein Trick, um den Compiler dazu zu bringen, ungültige Eingaben zu sperren.
Überlegen Sie
BUILD_BUG_ON_ZERO
. Wenn-!!(e)
ein negativer Wert ausgewertet wird, entsteht ein Kompilierungsfehler. Andernfalls wird-!!(e)
0 ausgewertet, und ein Bitfeld mit einer Breite von 0 hat die Größe 0. Daher wird das Makrosize_t
mit dem Wert 0 a ausgewertet .Der Name ist meiner Ansicht nach schwach, da der Build tatsächlich fehlschlägt, wenn die Eingabe nicht Null ist.
BUILD_BUG_ON_NULL
ist sehr ähnlich, liefert aber eher einen Zeiger als einenint
.quelle
sizeof(struct { int:0; })
streng konform?0
? Astruct
mit nur einem leeren Bitfeld, stimmt, aber ich denke nicht, dass Strukturen mit der Größe 0 erlaubt sind. Wenn Sie beispielsweise ein Array dieses Typs erstellen möchten, müssen die einzelnen Array-Elemente immer noch unterschiedliche Adressen haben, nicht wahr?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."
So ist zum Beispielsizeof (struct {int a:1; int:0;})
streng konform, abersizeof(struct { int:0; })
nicht (undefiniertes Verhalten).Einige Leute scheinen diese Makros mit zu verwechseln
assert()
.Diese Makros implementieren einen Test zur Kompilierungszeit, während
assert()
es sich um einen Laufzeit-Test handelt.quelle
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 beim Kompilieren des Funktionsaufrufs auf den Optimierer zu verlassen, wenn Ihre Behauptung korrekt ist.
Während dieser Mechanismus funktioniert (solange Optimierungen aktiviert sind), hat er den Nachteil, dass er erst beim Verknüpfen einen Fehler meldet. Zu diesem Zeitpunkt findet er die Definition für die Funktion you_did_something_bad () nicht. Aus diesem Grund beginnen Kernel-Entwickler mit Tricks wie den Bitfeldbreiten mit negativer Größe und den Arrays mit negativer Größe (von denen die späteren Builds in GCC 4.4 nicht mehr brechen).
In Sympathie für die Notwendigkeit von Zusicherungen zur Kompilierungszeit hat GCC 4.3 das
error
Funktionsattribut eingeführt , mit dem Sie dieses ältere Konzept erweitern können, aber einen Fehler zur Kompilierungszeit mit einer Nachricht Ihrer Wahl generieren können - kein kryptisches Array mit negativer Größe mehr " Fehlermeldungen!Tatsächlich haben wir ab Linux 3.9 ein Makro namens
compiletime_assert
, das diese Funktion verwendet, und die meisten Makrosbug.h
wurden entsprechend aktualisiert. Dieses Makro kann jedoch nicht als Initialisierer verwendet werden. Mit Anweisungsausdrücken (einer anderen GCC C-Erweiterung) können Sie jedoch!Dieses Makro wertet seinen Parameter genau einmal aus (falls es Nebenwirkungen hat) und erstellt einen Fehler bei der Kompilierung, der besagt: "Ich habe dir gesagt, du sollst mir keine Fünf geben!" wenn der Ausdruck fünf ergibt oder keine Kompilierungszeitkonstante ist.
Warum verwenden wir dies 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 konstante Initialisierer (für Enum-Konstanten, Bitfeldbreite usw.), selbst wenn der Anweisungsausdruck selbst vollständig konstant ist (dh vollständig ausgewertet werden kann) zur Kompilierungszeit und besteht ansonsten den
__builtin_constant_p()
Test). Außerdem 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 rechtlicher konstanter Ausdruck ist. C ++ 11 hat das Schlüsselwort constexpr nur für diesen Typ oder dieses Objekt hinzugefügt, aber in C11 ist kein Gegenstück vorhanden. Während C11 statische Zusicherungen 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 und deren Verwendung für Anweisungsausdrücke et ermöglichen kann. al.
quelle
so the expression can be used e.g. in a structure initializer (or where-ever else comma expressions aren't permitted).
" Das Makro gibt einen Ausdruck vom Typ zurücksize_t
error: bit-field ‘<anonymous>’ width not an integer constant
Es erlaubt nur Konstanten. Also, was nützt das?Es wird ein
0
Größenbitfeld erstellt, wenn die Bedingung falsch ist, aber ein Größenbitfeld-1
(-!!1
), wenn die Bedingung wahr / ungleich Null ist. Im ersteren Fall liegt kein Fehler vor und die Struktur wird mit einem int-Member initialisiert. Im letzteren Fall liegt ein Kompilierungsfehler vor (und es wird-1
natürlich kein Größenbitfeld erstellt).quelle
size_t
Wert mit dem Wert 0 zurückgegeben, falls die Bedingung erfüllt ist.