Vraag Wat is ": - !!" in C-code?


Ik kwam deze vreemde macrocode tegen /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); }))

Wat doet :-!! do?


1476
2018-02-10 14:50


oorsprong


antwoorden:


Dit is in feite een manier om te controleren of de uitdrukking e kan worden geëvalueerd als 0, en zo niet, om de build te mislukken.

De macro is enigszins verkeerd benoemd; het zou iets meer moeten zijn BUILD_BUG_OR_ZERO, liever dan ...ON_ZERO. (Er zijn geweest af en toe discussiëren of dit een verwarrende naam is.)

Je zou de uitdrukking als volgt moeten lezen:

sizeof(struct { int: -!!(e); }))
  1. (e): Bereken expressie e.

  2. !!(e): Logisch twee keer ontkennen: 0 als e == 0; anders- 1.

  3. -!!(e): Maak de uitdrukking uit stap 2 numeriek teniet: 0 Als het was 0; anders- -1.

  4. struct{int: -!!(0);} --> struct{int: 0;}: Als het nul was, dan declareren we een struct met een anoniem integer bitfield dat de breedte nul heeft. Alles is in orde en we gaan gewoon door.

  5. struct{int: -!!(1);} --> struct{int: -1;}: Aan de andere kant, als het is niet nul, dan is het een negatief getal. Een bitveld aangeven met negatief width is een compilatiefout.

We eindigen dus met een bitveld met de breedte 0 in een struct, wat prima is, of een bitveld met een negatieve breedte, wat een compilatiefout is. Dan nemen we sizeof dat veld, dus we krijgen een size_t met de juiste breedte (die nul zal zijn in het geval waar e is nul).


Sommige mensen hebben gevraagd: Waarom niet gewoon een assert?

keithmo's antwoord hier heeft een goede reactie:

Deze macro's implementeren een compilatietest, terwijl beweren () een runtime-test is.

Precies goed. U wilt geen problemen in uw pit tijdens runtime dat eerder had kunnen worden gevangen! Het is een cruciaal onderdeel van het besturingssysteem. In welke mate problemen ook tijdens het compileren kunnen worden gedetecteerd, des te beter.


1530
2018-02-10 15:04



De : is een bitveld. Wat betreft !!, dat is logische dubbele ontkenning en dus keert terug 0 voor false of 1 echt gebeurd. En de - is een minteken, d.w.z. rekenkundige negatie.

Het is allemaal een truc om de compiler te laten barf op ongeldige ingangen.

Overwegen BUILD_BUG_ON_ZERO. Wanneer -!!(e) evalueert naar een negatieve waarde, die een compileerfout oplevert. Anders -!!(e) evalueert tot 0, en een 0-breed bitveld heeft de grootte van 0. En daarom evalueert de macro naar a size_t met waarde 0.

De naam is zwak in mijn ogen omdat de build in feite faalt wanneer de invoer is niet nul.

BUILD_BUG_ON_NULL is vergelijkbaar, maar geeft een aanwijzer in plaats van een int.


235
2018-02-10 14:54



Sommige mensen lijken deze macro's te verwarren met assert().

Deze macro's implementeren een compilatietest, terwijl assert() is een runtime-test.


149
2018-02-10 15:37



Nou, ik ben behoorlijk verrast dat de alternatieven voor deze syntaxis niet zijn genoemd. Een ander veelgebruikt (maar ouder) mechanisme is om een ​​functie aan te roepen die niet is gedefinieerd en te vertrouwen op de optimizer om de functieaanroep samen te stellen als uw bewering juist is.

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

Hoewel dit mechanisme werkt (zolang optimalisaties zijn ingeschakeld), heeft het de keerzijde van het niet melden van een fout totdat u een koppeling maakt. Op dat moment wordt de definitie voor de functie you_did_something_bad () niet gevonden. Daarom beginnen kernelontwikkelaars trucs te gebruiken zoals de bit-veld-breedten met negatief formaat en de arrays met een ongunstige grootte (waarvan de latere in 4.4.4 niet meer breekt in GCC 4.4).

Sympathie voor de noodzaak van compileertijd-beweringen, GCC 4.3 introduceerde de error functieattribuut waarmee je dit oudere concept kunt uitbreiden, maar een compile-time error kunt genereren met een bericht naar keuze - geen cryptische "negative array" -foutmeldingen meer!

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

Eigenlijk, vanaf Linux 3.9, hebben we nu een macro genaamd compiletime_assert waarin deze functie en de meeste macro's worden gebruikt bug.h zijn dienovereenkomstig bijgewerkt. Toch kan deze macro niet als een initialisator worden gebruikt. Echter gebruiken door uitdrukkingen (een andere GCC C-extensie), dat kan!

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

Deze macro zal de parameter exact één keer evalueren (in het geval het bijwerkingen heeft) en een fout in de compilatie maken die zegt: "Ik zei dat je me geen vijf moest geven!" als de expressie evalueert tot vijf of geen compilatieconstante is.

Dus waarom gebruiken we dit niet in plaats van negatief-sized bit-velden? Helaas zijn er momenteel veel beperkingen aan het gebruik van uitdrukkingen, inclusief het gebruik ervan als constante initializers (voor enum-constanten, bit-veldbreedte, etc.), zelfs als de uitdrukkingsuitdrukking volledig zelf is (dat wil zeggen, volledig kan worden geëvalueerd) tijdens het compileren en anderszins de __builtin_constant_p() test). Verder kunnen ze niet buiten een functionele instantie worden gebruikt.

Hopelijk zal GCC deze tekortkomingen binnenkort wijzigen en constante uitdrukkingen toestaan ​​als constante initializers te gebruiken. De uitdaging hier is de taalspecificatie die definieert wat een juridische constante uitdrukking is. C ++ 11 heeft het constexpr-sleutelwoord toegevoegd voor alleen dit type of ding, maar er bestaat geen tegenhanger in C11. Hoewel C11 wel statische beweringen kreeg, wat een deel van dit probleem zal oplossen, lost het niet al deze tekortkomingen op. Dus ik hoop dat gcc een constexpr-functionaliteit beschikbaar kan maken als een uitbreiding via -std = gnuc99 & -std = gnuc11 of iets dergelijks en het gebruik ervan toestaan ​​op statement expressies et. al.


42
2018-06-27 08:21



Het maakt een maat aan 0 bitveld als de voorwaarde onwaar is, maar een grootte -1 (-!!1) bitveld als de voorwaarde waar / niet nul is. In het eerste geval is er geen fout en wordt de struct geïnitialiseerd met een int-lid. In het laatste geval is er een compileerfout (en niet zoiets als een grootte -1 bitfield is natuurlijk gemaakt).


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