Cosa è ":-!!" nel codice C?


1665

Ho incontrato questo strano codice macro 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
   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); }))

Cosa fa :-!!?


2
- Unario meno <br />! Logico NON <br /> non inverso non dell'intero dato e quindi la variabile può essere 0 o 1.
CyrillC

69
git blame ci dice che questa particolare forma di asserzione statica è stata introdotta da Jan Beulich in 8c87df4 . Ovviamente aveva buone ragioni per farlo (vedi il messaggio di commit).
Niklas B.

55
@Lundin: assert () NON causa un errore di compilazione. Questo è il punto centrale della costruzione di cui sopra.
Chris Pacejo,

4
@GreweKokkor Non essere ingenuo, Linux è troppo grande per essere gestito da una sola persona. Linus ha i suoi luogotenenti e loro hanno i loro che spingono cambiamenti e miglioramenti dal basso verso l'alto. Linus decide solo se desidera o meno funzionalità, ma si fida in parte dei colleghi. Se vuoi saperne di più su come funziona il sistema distribuito in ambiente open source, controlla il video di YouTube: youtube.com/watch?v=4XpnKHJAok8 (È un discorso molto interessante).
Tomas Pruzina,

3
@cpcloud, sizeof"valuta" il tipo, ma non il valore. È il tipo che non è valido in questo caso.
Winston Ewert,

Risposte:


1692

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 erroneamente denominata; dovrebbe essere qualcosa di più simile 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): Calcola espressione e.

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

  3. -!!(e): Annulla numericamente l'espressione dal passaggio 2: 0se lo era 0; altrimenti -1.

  4. struct{int: -!!(0);} --> struct{int: 0;}: Se era zero, allora dichiariamo uno 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, sarà un numero negativo. Dichiarare qualsiasi campo di bit con larghezza negativa è un errore di compilazione.

Quindi finiremo con un bitfield che ha larghezza 0 in una struttura, il che va bene, o un bitfield con larghezza negativa, che è un errore di compilazione. Quindi prendiamo sizeofquel campo, quindi otteniamo un size_tcon la larghezza appropriata (che sarà zero nel caso in cui esia 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 di compilazione, mentre assert () è un test di runtime.

Completamente giusto. Non vuoi rilevare problemi nel tuo kernel in fase di esecuzione che potrebbero essere stati rilevati in precedenza! È un elemento critico del sistema operativo. In qualunque misura i problemi possano essere rilevati in fase di compilazione, tanto meglio.


5
@weston Molti posti diversi. Guarda tu stesso!
John Feminella,

166
recenti varianti di standard C ++ o C hanno qualcosa di simile static_assertper scopi correlati.
Basile Starynkevitch,

54
@Lundin - #error richiederebbe l'uso di 3 righe di codice # if / # error / # endif e funzionerebbe solo per le valutazioni accessibili al pre-processore. Questo hack funziona per qualsiasi valutazione accessibile al compilatore.
Ed Staub,

236
Il kernel di Linux non usa C ++, almeno non mentre Linus è ancora vivo.
Mark Ransom,

6
@ Dolda2000: " Le espressioni booleane in C sono definite per valutare sempre zero o uno " - Non esattamente. Gli operatori che producono risultati "logicamente boolean" ( !, <, >, <=, >=, ==, !=, &&, ||) cedono sempre 0 o 1. Altre espressioni possono dare risultati possono essere utilizzati come condizioni, ma sono semplicemente zero o diverso da zero; per esempio, isdigit(c)dove cè una cifra, può produrre qualsiasi valore diverso da zero (che viene quindi trattato come vero in una condizione).
Keith Thompson,

256

Il :è un campo di bit. Per quanto riguarda !!, questa è la doppia negazione logica e quindi ritorna 0per falso o 1per vero. E -c'è un segno meno, cioè una negazione aritmetica.

È solo un trucco per ottenere il compilatore su input non validi.

Prendere in considerazione BUILD_BUG_ON_ZERO. Quando -!!(e)restituisce un valore negativo, viene generato un errore di compilazione. Altrimenti -!!(e)restituisce 0 e un campo di bit con larghezza 0 ha una dimensione di 0. E quindi la macro restituisce un size_tvalore con 0.

Il nome è debole dal mio punto di vista perché la build in realtà fallisce quando l'input non è zero.

BUILD_BUG_ON_NULLè molto simile, ma produce un puntatore anziché un int.


14
è sizeof(struct { int:0; })rigorosamente conforme?
ouah

7
Perché il risultato dovrebbe essere in generale 0? A structcon solo un campo di bit vuoto, vero, ma non credo che sono ammessi struct con dimensione 0. Ad esempio, se si crea un array di quel tipo, i singoli elementi dell'array devono comunque avere indirizzi diversi, no?
Jens Gustedt,

2
in realtà non gliene importa come usano le estensioni GNU, disabilitano una rigida regola di aliasing e non considerano gli overflow di interi come UB. Ma mi chiedevo se questo è strettamente conforme a C.
ouah

3
@ouah per quanto riguarda i bitfield senza nome a lunghezza zero, vedere qui: stackoverflow.com/questions/4297095/…
David Heffernan,

9
@DavidHeffernan in realtà C consente bit-field senza 0larghezza di larghezza, ma non se non ci sono altri membri nominati nella struttura. (C99, 6.7.2.1p2) "If the struct-declaration-list contains no named members, the behavior is undefined."Quindi, per esempio, sizeof (struct {int a:1; int:0;})è strettamente conforme ma sizeof(struct { int:0; })non lo è (comportamento indefinito).
ouah,

168

Alcune persone sembrano confondere queste macro con assert().

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


52

Bene, sono abbastanza sorpreso che le alternative a questa sintassi non siano state menzionate. Un altro meccanismo comune (ma meno recente) è chiamare una funzione non definita e fare affidamento sull'ottimizzatore per compilare la chiamata di funzione se la tua affermazione è 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 (fintanto che le ottimizzazioni sono abilitate) ha il rovescio della medaglia di non segnalare un errore fino al collegamento, a quel punto non riesce a trovare la definizione per la funzione you_did_something_bad (). Ecco perché gli sviluppatori del kernel iniziano a usare trucchi come le larghezze di campo bit di dimensioni negative e le matrici di dimensioni negative (la parte successiva ha smesso di interrompere le build in GCC 4.4).

In sintonia con la necessità di asserzioni in fase di compilazione, GCC 4.3 ha introdotto l' errorattributo della funzione che consente di estendere questo concetto precedente, ma di generare un errore in fase di compilazione con un messaggio di propria scelta - non più "criptico" array di dimensioni negative " messaggio 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)

In effetti, a partire da Linux 3.9, ora abbiamo una macro chiamata compiletime_assertche utilizza questa funzionalità e la maggior parte delle macro inbug.h sono state aggiornate di conseguenza. Tuttavia, questa macro non può essere utilizzata come inizializzatore. Tuttavia, usando le espressioni statement (un'altra estensione C di GCC), puoi!

#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 di compilazione che dice "Ti ho detto di non darmi un cinque!" se l'espressione restituisce cinque o non è una costante di compilazione.

Quindi perché non stiamo usando questo invece di campi bit di dimensioni negative? Purtroppo, al momento ci sono molte restrizioni all'uso delle espressioni delle istruzioni, incluso il loro uso come inizializzatori costanti (per costanti di enum, larghezza del campo di bit, ecc.) Anche se l'espressione dell'istruzione è completamente costante il suo sé (cioè, può essere valutato completamente in fase di compilazione e in caso contrario supera il __builtin_constant_p()test). Inoltre, non possono essere utilizzati al di fuori di un corpo di funzione.

Eventualmente, GCC modificherà presto queste carenze e consentirà di utilizzare espressioni di istruzioni 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 proprio per questo tipo o cosa, ma non esiste alcuna controparte in C11. Mentre C11 ha ottenuto affermazioni statiche, che risolveranno parte di questo problema, non risolverà tutte queste carenze. Quindi spero che gcc possa rendere disponibile una funzionalità constexpr come estensione tramite -std = gnuc99 & -std = gnuc11 o alcuni di questi e consentirne l'uso su espressioni di istruzioni et. al.


6
Tutte le tue soluzioni NON sono alternative. Il commento sopra la macro è piuttosto chiaro " so the expression can be used e.g. in a structure initializer (or where-ever else comma expressions aren't permitted)." La macro restituisce un'espressione di tiposize_t
Wiz

3
@Wiz Sì, ne sono consapevole. Forse questo è stato un po 'prolisso e forse ho bisogno di visitare nuovamente la mia formulazione, ma il mio punto era esplorare i vari meccanismi per asserzioni statiche e mostrare perché stiamo ancora usando bitfield di dimensioni negative. In breve, se otteniamo un meccanismo per l'espressione costante dell'istruzione, avremo altre opzioni aperte.
Daniel Santos,

Comunque non possiamo usare queste macro per una variabile. destra? error: bit-field ‘<anonymous>’ width not an integer constantPermette solo costanti. Allora, a che serve?
Karthik Raj Palanichamy,

1
@Karthik Cerca nelle fonti del kernel Linux per vedere perché viene usato.
Daniel Santos,

@supercat Non vedo come il tuo commento sia affatto correlato. Puoi per favore rivederlo, spiegare meglio cosa intendi o rimuoverlo?
Daniel Santos,

36

Sta creando un 0campo di dimensioni se la condizione è falsa, ma un campo di dimensioni -1( -!!1) se la condizione è vera / diversa da zero. Nel primo caso, non si verificano errori e la struttura viene inizializzata con un membro int. In quest'ultimo caso, c'è un errore di compilazione (e -1ovviamente non viene creato un bitfield di dimensione ).


3
In realtà sta restituendo a size_tcon valore 0 nel caso in cui la condizione sia vera.
David Heffernan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.