Je suis tombé sur cette étrange macro-code 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 s'agit, en fait, d'un moyen de vérifier si l'expression e peut être évaluée à 0, et si ce n'est pas le cas, de faire échouer la construction.
La macro est quelque peu mal nommée ; elle devrait être quelque chose comme BUILD_BUG_OR_ZERO
, plutôt que ...ON_ZERO
. (Il y a eu [des discussions occasionnelles sur le fait que ce nom prête à confusion][1].)
Vous devriez lire l'expression comme ceci :
sizeof(struct { int: -!!(e); }))
(e)
: Calculer l'expression e
.
!!(e)
: Négocier logiquement deux fois : 0
si e == 0
; sinon 1
.
- !!(e)
: Négation numérique de l'expression de l'étape 2 : 0
si c'était 0
; sinon -1
.
struct{int : - !!(0);} --> struct{int : 0;}
: Si c'est zéro, alors nous déclarons une structure avec un champ de bits entier anonyme de largeur zéro. Tout va bien et nous procédons normalement.
struct{int : - !!(1);} --> struct{int : -1;}
: D'un autre côté, s'il n'est pas zéro, alors ce sera un nombre négatif. Déclarer un champ de bits avec une largeur négative est une erreur de compilation.
Nous nous retrouverons donc soit avec un champ de bits de largeur 0 dans un struct, ce qui est bien, soit avec un champ de bits de largeur négative, ce qui est une erreur de compilation. Ensuite, nous prenons sizeof
de ce champ, de sorte que nous obtenons un size_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's ici a une bonne réponse :
Ces macros implémentent un test à la compilation, alors que assert() est un test à l'exécution.
C'est tout à fait exact. Vous ne voulez pas détecter des problèmes dans votre noyau au moment de l'exécution qui auraient pu être détectés plus tôt ! C’est un élément critique du système d’exploitation. Dans la mesure où les problèmes peuvent être détectés au moment de la compilation, c'est encore mieux.
[1] : http://lkml.indiana.edu/hypermail/linux/kernel/0703.1/1546.html
Le :
est un champ de bits. Quant à !!
, il s'agit de [double négation logique][1] et renvoie donc 0
pour faux ou 1
pour vrai. Et le -
est un signe moins, c'est-à-dire une négation arithmétique.
Tout cela n'est qu'une astuce pour que le compilateur se rebiffe en cas d'entrées invalides.
Considérez BUILD_BUG_ON_ZERO
. Lorsque - !!(e)
est évalué à une valeur négative, cela produit une erreur de compilation. Sinon, - !!(e)
vaut 0, et un bitfield de largeur 0 a une taille de 0. Et donc la macro vaut un size_t
de valeur 0.
Le nom est faible à mon avis parce que la compilation échoue en fait quand l'entrée est non zéro.
BUILD_BUG_ON_NULL
est très similaire, mais donne un pointeur plutôt qu'un int
.
[1] : https://stackoverflow.com/questions/248693/double-negation-in-c-code
Il crée un champ de bits de taille 0
si la condition est fausse, mais un champ de bits de taille -1
(-!!1
) si la condition est vraie/non-zéro. Dans le premier cas, il n'y a pas d'erreur et la structure est initialisée avec un membre int. Dans le second cas, il y a une erreur de compilation (et aucun champ binaire de taille -1
n'est créé, bien sûr).