Qu'est-ce que ":-!!"dans le code C?

je suis tombé sur cet étrange code 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 :-!! ?

1503
demandé sur mpen 2012-02-10 18:50:08
la source

6 ответов

C'est, en effet, une manière de vérifier si l'expression e peut être évalué à 0, et si non, l'échec de la compilation .

la macro est un 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 pour savoir s'il s'agit d'un nom confus .)

Vous devriez lire l'expression comme ceci:

sizeof(struct { int: -!!(e); }))
  1. (e) : calculer l'expression e .

  2. !!(e) : nier logiquement deux fois: 0 si e == 0 ; sinon 1 .

  3. -!!(e) : annuler numériquement l'expression de l'étape 2: 0 si elle était 0 ; autrement -1 .

  4. struct{int: -!!(0);} --> struct{int: 0;} : si elle était zéro, alors nous déclarons une struct avec un bitfield entier anonyme qui a la largeur zéro. Tout va bien et nous procédons comme d'habitude.

  5. struct{int: -!!(1);} --> struct{int: -1;} : d'un autre côté, si n'est pas zéro, alors ce sera un nombre négatif. Déclarer n'importe quel bitfield avec négatif largeur est une erreur de compilation.

donc soit nous finirons avec un bitfield qui a la largeur 0 dans une struct, ce qui est bien, ou un bitfield avec la largeur négative, ce qui est une erreur de compilation. Ensuite nous prenons sizeof ce champ, donc 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 ?

keithmo la réponse de a ici une bonne réponse:

ces macros implémentent un test de compilation, tandis que assert() est un test d'exécution.

exact. Vous ne voulez pas détecter de problèmes dans votre noyau à l'exécution qui aurait pu être pris plus tôt! C'est une pièce essentielle du système d'exploitation. Autant de problèmes peuvent être détectés au moment de la compilation, d'autant mieux.

1550
répondu John Feminella 2017-05-23 14:55:00
la source

le : est un bitfield. Comme pour !! , c'est-à-dire double négation logique et retourne ainsi 0 pour faux ou 1 pour vrai. Et le - est un signe négatif, c'est-à-dire une négation arithmétique.

C'est juste un truc pour que le compilateur barf entrées non valides.

prendre en considération BUILD_BUG_ON_ZERO . Lorsque -!!(e) est évalué à une valeur négative, cela produit une erreur de compilation. Autrement -!!(e) évalue à 0, et un bitfield 0 Largeur a la taille de 0. Et donc la macro évalue à un size_t avec la valeur 0.

le nom est faible à mon avis parce que la compilation échoue en fait lorsque l'entrée est et non zéro.

BUILD_BUG_ON_NULL est très similaire, mais renvoie un pointeur plutôt qu'un int .

240
répondu David Heffernan 2017-05-23 15:34:45
la source

certaines personnes semblent confondre ces macros avec assert() .

ces macros implémentent un test de compilation, tandis que assert() est un test d'exécution.

151
répondu keithmo 2017-03-09 01:22:41
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) est d'appeler une fonction qui n'est pas définie et de compter sur l'optimiseur pour compiler l'appel de la fonction si votre assertion est correcte.

#define MY_COMPILETIME_ASSERT(test)              \
    do {                                         \
        extern void you_did_something_bad(void); \
        if (!(test))                             \
            you_did_something_bad(void);         \
    } while (0)

bien que ce mécanisme fonctionne (aussi longtemps que les optimisations sont activées) il a l'inconvénient de ne pas signaler une erreur jusqu'à ce que vous liez, à ce moment-là, il ne parvient pas à trouver la définition pour le fonction you_did_something_bad(). C'est pourquoi les développeurs du noyau commencent à utiliser des astuces comme les largeurs de champ bit de taille négative et les tableaux de taille négative (dont la dernière a cessé de casser les constructions dans GCC 4.4).

en sympathie pour le besoin d'assertions de compilation, GCC 4.3 a présenté le error attribut de fonction qui vous permet d'étendre sur ce concept plus ancien, mais générer une erreur de compilation avec un message de votre choisir -- plus de messages d'erreur cryptiques de "tableau de taille négative"!

#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)

en fait, à partir de Linux 3.9, nous avons maintenant une macro appelée compiletime_assert qui utilise cette fonctionnalité et la plupart des macros dans bug.h ont été mis à jour en conséquence. Cependant, cette macro ne peut pas être utilisée comme initialiseur. Toutefois, en utilisant par les expressions de déclaration (un autre GCC C-extension), vous pouvez!

#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;                                                    \
    })

cette macro va évaluer son paramètre exactement une fois (au cas où il y aurait des effets secondaires) et créer une erreur de compilation qui dit "je vous ai dit de ne pas me donner un cinq!"si l'expression est évaluée à cinq ou n'est pas une constante de compilation.

alors pourquoi ne pas utiliser ceci au lieu de champs de taille négative? Hélas, il existe actuellement de nombreuses restrictions à l'utilisation des expressions de déclaration, y compris leur utilisation en tant que initialisateurs constants (pour les constantes enum, la largeur du champ bit, etc.) même si l'expression de la déclaration est complètement constante (c'est-à-dire qu'elle peut être évaluée complètement au moment de la compilation et qu'elle passe le test __builtin_constant_p() ). De plus, ils ne peuvent pas être utilisés en dehors d'un organisme fonctionnel.

avec un peu de chance, GCC va bientôt corriger ces lacunes et permettre que des expressions de déclaration constantes soient utilisées comme initialisateurs constants. Le défi ici est la spécification linguistique définissant ce qui est une expression constante juridique. C++11 a ajouté le mot-clé constexpr pour seulement 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é de constexpr disponible en tant qu'extension via-std=gnuc99 & -std=gnuc11 ou une autre et permettre son utilisation sur statement expressions et. Al.

42
répondu Daniel Santos 2013-07-18 02:08:28
la source

il crée une taille 0 bitfield si la condition est false, mais une taille -1 ( -!!1 ) bitfield si la condition est true/non-zero. Dans le premier cas, il n'y a pas d'erreur et la structure est initialisé avec un int membre. Dans ce dernier cas, il y a une erreur de compilation (et rien de tel qu'un bitfield de taille -1 n'est créé, bien sûr).

31
répondu Matt Phillips 2012-02-10 18:56:35
la source
 Linux Kernel :   

/* 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); }))
-4
répondu leesagacious 2018-06-24 14:07:40
la source

Autres questions sur linux c macros linux-kernel