Question Qu'est-ce que ": - !!" en code C?


Je suis tombé sur 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); }))

Qu'est-ce que :-!! faire?


1476
2018-02-10 14:50


origine


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 un peu mal nommée; ça devrait être quelque chose de plus BUILD_BUG_OR_ZERO, plutôt que ...ON_ZERO. (Il y a eu discussions occasionnelles pour savoir si c'est un nom déroutant.)

Vous devriez lire l'expression comme ceci:

sizeof(struct { int: -!!(e); }))
  1. (e): Expression de calcul e.

  2. !!(e): Nier logiquement deux fois: 0 si e == 0; autrement 1.

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

  4. struct{int: -!!(0);} --> struct{int: 0;}: Si elle était nulle, nous déclarons une structure avec un bitfield entier anonyme de 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 un champ de bits avec négatif width est une erreur de compilation.

Donc nous finirons soit avec un bitfield de largeur 0 dans un struct, ce qui est bien, soit un bitfield avec une largeur négative, ce qui est une erreur de compilation. Ensuite, nous prenons sizeof ce champ, nous obtenons un size_t avec la largeur appropriée (qui sera nulle 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:

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

Exactement juste. Vous ne voulez pas détecter les problèmes dans votre noyau à l'exécution qui aurait pu être pris plus tôt! C'est un élément essentiel du système d'exploitation. Dans quelle mesure les problèmes peuvent être détectés au moment de la compilation, tant mieux.


1530
2018-02-10 15:04



le : est un bitfield. Pour ce qui est de !!, C'est double négation logique et donc retourne 0 pour faux ou 1 Pour de vrai. Et le - est un signe moins, c'est-à-dire une négation arithmétique.

Tout est juste une astuce pour obtenir le compilateur à barf sur les entrées invalides.

Considérer BUILD_BUG_ON_ZERO. Quand -!!(e) évalue à une valeur négative, qui produit une erreur de compilation. Autrement -!!(e) évalue à 0, et un champ de largeur 0 a une taille de 0. Et donc la macro évalue à un size_t avec la valeur 0.

Le nom est faible à mon avis parce que la construction échoue en fait quand l'entrée est ne pas zéro.

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


235
2018-02-10 14:54



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.


149
2018-02-10 15:37



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.

#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 (tant que les optimisations sont activées), il a l'inconvénient de ne pas signaler une 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 la dernière a cessé de générer des builds dans GCC 4.4).

En accord avec la nécessité d'établir des assertions à la compilation, GCC 4.3 a introduit le error attribut de fonction cela vous permet de prolonger ce concept plus ancien, mais de générer une erreur de compilation avec un message de votre choix - plus de message d'erreur cryptique "negative sized array"!

#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, depuis 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 un initialiseur. Cependant, en utilisant par expressions d'instruction (une autre extension C de GCC), 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 une seule fois (au cas où il aurait des effets secondaires) et créer une erreur de compilation qui dit "Je vous ai dit de ne pas m'en donner un cinq!" si l'expression est évaluée à cinq ou n'est pas une constante de compilation.

Alors pourquoi n'utilisons-nous pas cela à la place des champs de bits de taille négative? Hélas, il existe actuellement de nombreuses restrictions d'utilisation des expressions d'instruction, y compris leur utilisation en tant qu'initialiseurs constants (pour les constantes enum, largeur de champ de bits, etc.) même si l'expression de l'expression est complètement constante (ie, peut être entièrement évaluée à la compilation et passe le __builtin_constant_p() tester). En outre, ils ne peuvent pas être utilisés en dehors d'un corps de fonction.

Il est à espérer que GCC modifiera bientôt ces lacunes et permettra d'utiliser des expressions de déclaration 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 dans C11. Tandis que C11 a obtenu des assertions statiques, ce qui résoudra une partie de ce problème, il ne résoudra pas toutes ces imperfections. Donc j'espère que gcc peut rendre une fonctionnalité constexpr disponible en extension via -std = gnuc99 & -std = gnuc11 ou une autre et permettre son utilisation sur les expressions d'instruction et. Al.


42
2018-06-27 08:21



C'est en train de créer une taille 0 bitfield si la condition est fausse, mais une taille -1 (-!!1) bitfield 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 pas de taille -1 bitfield est créé, bien sûr).


31
2018-02-10 14:54



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

-1
2018-06-21 07:18