GNU/Linux >> Linux Esercitazione >  >> Linux

Cos'è :-!! in codice C?

Alcune persone sembrano confondere queste macro con assert() .

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


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

La macro ha un nome in qualche modo errato; dovrebbe essere qualcosa di più simile a BUILD_BUG_OR_ZERO , anziché ...ON_ZERO . (Ci sono state discussioni occasionali sul fatto che questo sia un nome confuso .)

Dovresti leggere l'espressione in questo modo:

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

  2. !!(e) :Logicamente nega due volte:0 se e == 0; altrimenti 1 .

  3. -!!(e) :nega numericamente l'espressione del passaggio 2:0 se fosse 0; altrimenti -1 .

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

  5. struct{int: -!!(1);} --> struct{int: -1;} :D'altra parte, se non lo è zero, allora sarà un numero negativo. Dichiarare qualsiasi campo di bit con negativo width è un errore di compilazione.

Quindi o finiremo con un bitfield che ha larghezza 0 in una struttura, 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 avrebbe potuto essere catturato prima! È un pezzo critico del sistema operativo. Per quanto i problemi possano essere rilevati in fase di compilazione, tanto meglio.


Bene, sono piuttosto sorpreso che le alternative a questa sintassi non siano state menzionate. Un altro meccanismo comune (ma più vecchio) consiste nel chiamare una funzione che 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)

Sebbene questo meccanismo funzioni (a condizione che le ottimizzazioni siano abilitate), ha lo svantaggio di non segnalare un errore fino a quando non si effettua il collegamento, momento in cui non riesce a trovare la definizione per la funzione you_did_something_bad(). Questo è il motivo per cui gli sviluppatori del kernel hanno iniziato a utilizzare trucchi come le larghezze di campi di bit di dimensioni negative e gli array di dimensioni negative (l'ultimo dei quali ha smesso di violare le build in GCC 4.4).

In simpatia per la necessità di asserzioni in fase di compilazione, GCC 4.3 ha introdotto il error attributo di funzione che ti consente di estendere questo vecchio concetto, ma genera un errore in fase di compilazione con un messaggio di tua scelta -- niente più criptici messaggi di errore "array di dimensioni negative"!

#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, a partire da Linux 3.9, ora abbiamo una macro chiamata compiletime_assert che utilizza questa funzione e la maggior parte delle macro in bug.h sono stati aggiornati di conseguenza. Tuttavia, questa macro non può essere utilizzata come inizializzatore. Tuttavia, utilizzando by espressioni di istruzioni (un'altra estensione GCC C), 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 in fase di compilazione che dice "Ti avevo detto di non darmi un cinque!" se l'espressione restituisce cinque o non è una costante in fase di compilazione.

Allora perché non stiamo usando questo invece di campi di bit di dimensioni negative? Purtroppo, attualmente ci sono molte restrizioni sull'uso delle espressioni di istruzioni, incluso il loro uso come inizializzatori costanti (per costanti enum, larghezza del campo di bit, ecc.) anche se l'espressione dell'istruzione è completamente costante (cioè può essere completamente valutata in fase di compilazione e altrimenti passa __builtin_constant_p() test). Inoltre, non possono essere utilizzati al di fuori del corpo di una funzione.

Si spera che GCC corregga presto queste carenze e consenta l'uso di espressioni di istruzioni costanti come inizializzatori costanti. La sfida qui è la specifica del linguaggio che definisce cos'è 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. Sebbene C11 abbia ottenuto asserzioni 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 qualcosa di simile e consentirne l'uso su espressioni di istruzioni et. al.


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

È solo un trucco per far vomitare al compilatore input non validi.

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

Il nome è debole a mio avviso perché la compilazione in effetti fallisce quando l'input è not zero.

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


Linux
  1. Che cos'è un utente Linux?

  2. Che cos'è il Web 3.0?

  3. Come sapere cosa significa 'errno'?

  4. Cosa c'è di vulnerabile in questo codice C?

  5. Qual è il significato di EXPORT_SYMBOL nel codice del kernel Linux?

403 Errore proibito:cos'è e come risolverlo

Qual è l'errore 503 servizio non disponibile?

Che cos'è un errore interno del server 500?

Che cos'è un errore di servizio 503 non disponibile?

Errore del plug-in VPN Linux non riuscito - E adesso?

Cos'è Python:un'introduzione a un linguaggio di programmazione multipiattaforma