J'ai rencontré cet étrange code de macro dans /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); }))
Que fait :-!!
-il?
c
linux
macros
linux-kernel
chmurli
la source
la source
sizeof
"évalue" le type, mais pas la valeur. C'est le type qui n'est pas valide dans ce cas.Réponses:
C'est, en effet, un moyen de vérifier si l'expression e peut être évaluée à 0, et sinon, d'échouer la construction .
La macro est quelque peu mal nommée; ce devrait être quelque chose de plus
BUILD_BUG_OR_ZERO
, plutôt que...ON_ZERO
. (Il y a eu des discussions occasionnelles pour savoir s'il s'agit d'un nom déroutant .)Vous devriez lire l'expression comme ceci:
(e)
: Calculer l'expressione
.!!(e)
: Niez logiquement deux fois:0
sie == 0
; sinon1
.-!!(e)
: Nie numériquement l'expression de l'étape 2:0
si elle l'était0
; sinon-1
.struct{int: -!!(0);} --> struct{int: 0;}
: Si c'était zéro, alors nous déclarons une structure avec un champ de bits entier anonyme qui a une largeur nulle. Tout va bien et nous procédons normalement.struct{int: -!!(1);} --> struct{int: -1;}
: D'un autre côté, si ce n'est pas zéro, ce sera un nombre négatif. La déclaration de tout champ binaire de largeur négative est une erreur de compilation.Nous allons donc soit nous retrouver avec un champ de bits qui a une largeur 0 dans une structure, ce qui est bien, soit un champ de bits avec une largeur négative, ce qui est une erreur de compilation. Ensuite, nous prenons
sizeof
ce champ, nous obtenons donc unsize_t
avec la largeur appropriée (qui sera zéro dans le cas oùe
est zéro).Certaines personnes ont demandé: pourquoi ne pas simplement utiliser un
assert
?La réponse de Keithmo ici a une bonne réponse:
Exactement raison. Vous ne voulez pas détecter lors de l'exécution des problèmes dans votre noyau qui auraient pu être détectés plus tôt! C'est un élément essentiel du système d'exploitation. Dans toute la mesure du possible, des problèmes peuvent être détectés au moment de la compilation.
la source
static_assert
à des fins connexes.!
,<
,>
,<=
,>=
,==
,!=
,&&
,||
) Cédez toujours 0 ou 1. D' autres expressions peuvent donner des résultats qui peuvent être utilisés en tant que conditions, mais sont simplement nul ou non nul; par exemple,isdigit(c)
oùc
est un chiffre, peut donner n'importe quelle valeur non nulle (qui est alors traitée comme vraie dans une condition).C'est
:
un champ de bits. Quant à!!
, c'est une double négation logique et renvoie donc0
pour faux ou1
pour vrai. Et le-
est un signe moins, c'est-à-dire la négation arithmétique.C'est juste une astuce pour amener le compilateur à barf sur les entrées invalides.
Considérez
BUILD_BUG_ON_ZERO
. Lorsqu'évalue-!!(e)
à une valeur négative, cela produit une erreur de compilation. Sinon, il est-!!(e)
évalué à 0 et un champ de bits de largeur 0 a une taille de 0. Par conséquent, la macro est évaluée à unsize_t
avec une valeur 0.Le nom est faible à mon avis car la construction échoue en fait lorsque l'entrée n'est pas nulle.
BUILD_BUG_ON_NULL
est très similaire, mais donne un pointeur plutôt qu'unint
.la source
sizeof(struct { int:0; })
strictement conforme?0
? Unstruct
avec seulement un champ de bits vide, c'est vrai, mais je ne pense pas que les structures de taille 0 soient autorisées. Par exemple, si vous créez un tableau de ce type, les éléments de tableau individuels doivent toujours avoir des adresses différentes, non?0
non nommé de largeur, mais pas s'il n'y a aucun autre membre nommé dans la structure.(C99, 6.7.2.1p2) "If the struct-declaration-list contains no named members, the behavior is undefined."
Ainsi, par exemple, ilsizeof (struct {int a:1; int:0;})
est strictement conforme maissizeof(struct { int:0; })
ne l'est pas (comportement indéfini).Certaines personnes semblent confondre ces macros avec
assert()
.Ces macros implémentent un test au moment de la compilation, alors qu'il
assert()
s'agit d'un test d'exécution.la source
Eh bien, je suis assez surpris que les alternatives à cette syntaxe n'aient pas été mentionnées. Un autre mécanisme courant (mais plus ancien) consiste à appeler une fonction qui n'est pas définie et à s'appuyer sur l'optimiseur pour compiler l'appel de fonction si votre assertion est correcte.
Bien que ce mécanisme fonctionne (tant que les optimisations sont activées), il a l'inconvénient de ne pas signaler d'erreur jusqu'à ce que vous établissiez un lien, auquel cas il ne trouve pas la définition de la fonction you_did_something_bad (). C'est pourquoi les développeurs du noyau commencent à utiliser des astuces comme les largeurs de champ de bits de taille négative et les tableaux de taille négative (dont le dernier a cessé de casser les builds dans GCC 4.4).
Par sympathie pour le besoin d'assertions au moment de la compilation, GCC 4.3 a introduit l'
error
attribut de fonction qui vous permet d'étendre sur ce concept plus ancien, mais de générer une erreur au moment de la compilation avec un message de votre choix - plus de tableau de taille négative "plus cryptique" " messages d'erreur!En fait, depuis Linux 3.9, nous avons maintenant une macro appelée
compiletime_assert
qui utilise cette fonctionnalité et la plupart des macros dansbug.h
ont été mises à jour en conséquence. Pourtant, cette macro ne peut pas être utilisée comme initialiseur. Cependant, en utilisant des expressions par instruction (une autre extension C de GCC), vous pouvez!Cette macro évaluera son paramètre exactement une fois (au cas où elle aurait des effets secondaires) et créera une erreur de compilation qui dit "Je vous ai dit de ne pas m'en donner cinq!" si l'expression est évaluée à cinq ou n'est pas une constante de temps de compilation.
Alors pourquoi n'utilisons-nous pas cela au lieu de champs binaires de taille négative? Hélas, il existe actuellement de nombreuses restrictions à l'utilisation des expressions d'instruction, y compris leur utilisation comme initialiseurs constants (pour les constantes d'énumération, la largeur du champ de bits, etc.) même si l'expression d'instruction est complètement constante elle-même (c'est-à-dire qu'elle peut être entièrement évaluée au moment de la compilation et sinon passe le
__builtin_constant_p()
test). De plus, ils ne peuvent pas être utilisés en dehors d'un corps de fonction.Avec un peu de chance, GCC modifiera bientôt ces lacunes et permettra d'utiliser des expressions d'instruction constantes comme initialiseurs constants. Le défi ici est la spécification du langage définissant ce qu'est une expression constante légale. C ++ 11 a ajouté le mot clé constexpr pour ce type ou cette chose, mais aucune contrepartie n'existe en C11. Bien que C11 ait obtenu des assertions statiques, ce qui résoudra une partie de ce problème, il ne résoudra pas tous ces défauts. J'espère donc que gcc pourra rendre une fonctionnalité constexpr disponible en tant qu'extension via -std = gnuc99 & -std = gnuc11 ou quelque chose du genre et permettre son utilisation sur les expressions d'instructions et. Al.
la source
so the expression can be used e.g. in a structure initializer (or where-ever else comma expressions aren't permitted).
" La macro renvoie une expression de typesize_t
error: bit-field ‘<anonymous>’ width not an integer constant
Il n'autorise que les constantes. Alors, à quoi ça sert?Il crée un
0
champ binaire de taille si la condition est fausse, mais un champ binaire size-1
(-!!1
) si la condition est vraie / non nulle. Dans le premier cas, il n'y a pas d'erreur et la structure est initialisée avec un membre int. Dans ce dernier cas, il y a une erreur de compilation (et rien de tel qu'un-1
champ de bits de taille n'est créé, bien sûr).la source
size_t
avec une valeur 0 au cas où la condition est vraie.