Cosa significa “: – !!” nel codice C?

Mi sono imbattuto in questo strano codice in /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 eg 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); })) 

Cosa fa :-!! fare?

    Questo è, in effetti, un modo per verificare se l’espressione e può essere valutata come 0 e, in caso contrario, per fallire la compilazione .

    La macro è in qualche modo errata; dovrebbe essere qualcosa di più simile a BUILD_BUG_OR_ZERO , piuttosto che ...ON_ZERO . (Ci sono state discussioni occasionali sul fatto che si tratti di un nome confuso .)

    Dovresti leggere l’espressione in questo modo:

     sizeof(struct { int: -!!(e); })) 
    1. (e) : espressione di calcolo e .

    2. !!(e) : negare logicamente due volte: 0 se e == 0 ; altrimenti 1 .

    3. -!!(e) : annulla numericamente l’espressione del passaggio 2: 0 se era 0 ; altrimenti -1 .

    4. struct{int: -!!(0);} --> struct{int: 0;} : Se era zero, dichiariamo una struct con un bitfield intero anonimo con larghezza zero. Va tutto bene e procediamo normalmente.

    5. struct{int: -!!(1);} --> struct{int: -1;} : D’altra parte, se non è zero, allora sarà un numero negativo. Dichiarare qualsiasi bitfield con larghezza negativa è un errore di compilazione.

    Quindi finiremo con un bitfield che ha una larghezza 0 in una struct, che va bene, o un bitfield con larghezza negativa, che è un errore di compilazione. Quindi prendiamo sizeof quel campo, quindi otteniamo un size_t con la larghezza appropriata (che sarà zero nel caso in cui e è zero).


    Alcune persone hanno chiesto: Perché non usare solo un assert ?

    la risposta di keithmo qui ha una buona risposta:

    Queste macro implementano un test in fase di compilazione, mentre assert () è un test in fase di esecuzione.

    Completamente giusto. Non vuoi rilevare problemi nel tuo kernel in fase di esecuzione che potrebbero essere stati scoperti prima! È un pezzo fondamentale del sistema operativo. In qualunque misura i problemi possano essere rilevati al momento della compilazione, tanto meglio.

    Il : è un bitfield. Per quanto riguarda !! , che è la doppia negazione logica e quindi restituisce 0 per falso o 1 per vero. E il - è un segno meno, vale a dire la negazione aritmetica.

    È tutto solo un trucco per far sì che il compilatore faccia effetto su input non validi.

    Considera BUILD_BUG_ON_ZERO . Quando -!!(e) valuta un valore negativo, produce un errore di compilazione. Altrimenti -!!(e) restituisce 0 e un bitfield di larghezza 0 ha dimensione pari a 0. E quindi la macro size_t un valore size_t con valore 0.

    Il nome è debole a mio avviso perché la compilazione di fatto fallisce quando l’input non è zero.

    BUILD_BUG_ON_NULL è molto simile, ma produce un puntatore piuttosto che un int .

    Alcune persone sembrano confondere queste macro con assert() .

    Queste macro implementano un test in fase di compilazione, mentre assert() è un test di runtime.

    Bene, sono abbastanza sorpreso che le alternative a questa syntax non siano state menzionate. Un altro meccanismo comune (ma più vecchio) è quello di chiamare una funzione che non è definita e affidarsi all’ottimizzatore per compilare la chiamata della funzione se la propria asserzione è corretta.

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

    Mentre questo meccanismo funziona (purché le ottimizzazioni siano abilitate) ha il rovescio della medaglia di non riportare un errore finché non si collega, momento in cui non riesce a trovare la definizione per la funzione you_did_something_bad (). Ecco perché gli sviluppatori del kernel iniziano a utilizzare trucchi come le larghezze dei campi di bit di dimensioni negative e gli array di dimensioni negative (il più tardi ha smesso di generare build in GCC 4.4).

    In simpatia per la necessità di asserzioni in fase di compilazione, GCC 4.3 ha introdotto l’ attributo della funzione di error che consente di estendere questo concetto precedente, ma di generare un errore in fase di compilazione con un messaggio a scelta – non più criptico “negativo array “messaggi di errore!

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

    Infatti, da Linux 3.9, ora abbiamo una macro chiamata compiletime_assert che usa questa funzione e la maggior parte delle macro in bug.h sono state aggiornate di conseguenza. Tuttavia, questa macro non può essere utilizzata come inizializzatore. Tuttavia, utilizzando le espressioni di istruzioni (un’altra estensione C di GCC), è ansible!

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

    Questa macro valuterà il suo parametro esattamente una volta (nel caso abbia effetti collaterali) e creerà un errore in fase di compilazione che dice “Ti ho detto di non darmi un cinque!” se l’espressione è uguale a cinque o non è una costante in fase di compilazione.

    Quindi, perché non usiamo questo invece dei campi di bit di dimensioni negative? Purtroppo, ci sono attualmente molte restrizioni all’uso delle espressioni di istruzioni, incluso il loro uso come inizializzatori costanti (per costanti di enumerazione, larghezza del campo di bit, ecc.) Anche se l’espressione dell’istruzione è completamente costante il suo sé (cioè può essere pienamente valutata al momento della compilazione e altrimenti passa il test __builtin_constant_p() ). Inoltre, non possono essere utilizzati al di fuori di un corpo di una funzione.

    Si spera che GCC corregga presto queste carenze e consenta l’uso di espressioni di dichiarazione costanti come inizializzatori costanti. La sfida qui è la specifica del linguaggio che definisce ciò che è un’espressione costante legale. C ++ 11 ha aggiunto la parola chiave constexpr solo per questo tipo o cosa, ma non esiste alcuna controparte in C11. Mentre C11 ha ottenuto asserzioni statiche, che risolveranno parte di questo problema, non risolverà tutte queste carenze. Quindi spero che gcc possa rendere disponibile una funzionalità di constexpr come estensione via -std = gnuc99 & -std = gnuc11 o alcuni di essi e permetterne l’uso sulle espressioni di dichiarazione et. al.

    Sta creando un campo bit di dimensione 0 se la condizione è falsa, ma un -1 dimensione -1 ( -!!1 ) se la condizione è vera / diversa da zero. Nel primo caso, non vi è alcun errore e la struttura è inizializzata con un membro int. In quest’ultimo caso, vi è un errore di compilazione (e non viene creata una dimensione -1 bitfield, ovviamente).

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