Perché nessun avvertimento con “#if X” quando X indefinito?

A volte scrivo codice come questo:

// file1.cpp #define DO_THIS 1 #if DO_THIS // stuff #endif 

Durante lo sviluppo del codice, potrei cambiare la definizione di DO_THIS tra 0 e 1.

Recentemente ho dovuto riorganizzare il mio codice sorgente e copiare del codice da un file a un altro. Ma ho scoperto che avevo commesso un errore e le due parti erano state separate in questo modo:

 // file1.cpp #define DO_THIS 1 

e

 // file2.cpp #if DO_THIS // stuff #endif 

Ovviamente ho corretto l’errore, ma poi ho pensato a me stesso, perché il compilatore non mi ha avvisato? Ho il livello di avviso impostato su 4. Perché #if X non è sospetto quando X non è definito?

Un’altra domanda: esiste un modo sistematico in cui posso scoprire se ho fatto lo stesso errore altrove? Il progetto è enorme.

EDIT: Posso capire di non avere alcun avviso con #ifdef che ha perfettamente senso. Ma sicuramente #if è diverso.

gcc può generare un avviso per questo, ma probabilmente non è richiesto dallo standard:

-Wundef
Avverti se un identificatore non definito viene valutato in una direttiva `#if ‘.

Ancora una volta, come spesso accade, la risposta alla domanda “perché” è giusta: è stata fatta in quel modo perché qualche tempo fa si è deciso di farlo in questo modo. Quando si utilizza una macro non definita in un #if , viene sostituita con 0. Si desidera sapere se è effettivamente definita – utilizzare la direttiva defined() .

Ci sono però alcuni interessanti vantaggi per l’approccio “default to 0”. Soprattutto quando si utilizzano macro che potrebbero essere definite dalla piattaforma, non i propri macro.

Ad esempio, alcune piattaforms offrono macro __BYTE_ORDER , __LITTLE_ENDIAN e __BIG_ENDIAN per determinare la loro endianità. Si potrebbe scrivere una direttiva per il preprocessore come

 #if __BYTE_ORDER == __LITTLE_ENDIAN /* whatever */ #else /* whatever */ #endif 

Ma se provate a compilare questo codice su una piattaforma che non definisce affatto questi macro non standard (cioè non ne sa nulla), il codice sopra sarà tradotto dal preprocessore in

 #if 0 == 0 ... 

e la versione little-endian del codice verrà compilata “di default”. Se hai scritto l’originale #if come

 #if __BYTE_ORDER == __BIG_ENDIAN ... 

quindi la versione big-endian del codice verrebbe compilata “di default”.

Non posso dire che #if stato definito come specificamente per trucchi come sopra, ma a volte è utile.

Quando non è ansible utilizzare un compilatore con un messaggio di avviso (come -Wundef in gcc), ho trovato un modo un po ‘utile per generare errori del compilatore.

Ovviamente potresti sempre scrivere:

 #ifndef DO_THIS error #endif #if DO_THIS 

Ma questo è davvero fastidioso

Un metodo leggermente meno fastidioso è:

 #if (1/defined(DO_THIS) && DO_THIS) 

Questo genererà un errore di divisione per zero se DO_THIS non è definito. Questo metodo non è l’ideale perché l’identificatore è scritto due volte e un errore di ortografia nel secondo ci rimetterebbe dove abbiamo iniziato. Sembra strano anche. Sembra che ci dovrebbe essere un modo più pulito per realizzare questo, come:

 #define PREDEFINED(x) ((1/defined(x)) * x) #if PREDEFINED(DO_THIS) 

ma in realtà non funziona.

Se sei disperato di evitare questo tipo di errore, prova quanto segue che utilizza la valutazione di magia ed espressione del token-incapsulamento del preprocessore per far rispettare la definizione di una macro (e 0 in questo esempio):

 #define DEFINED_VALUE(x,y) (defined (y##x) ? x : 1/x) #if DEFINED_VALUE(FEATURE1,) == 0 

C’è un problema ricorsivo. Nel caso tu abbia

 #define MODEL MODEL_A #if (MODEL == MODEL_B) // Surprise, this is compiled! #endif 

dove mancano le definizioni di MODEL_A e MODEL_B, allora verrà compilato.

 #ifdef MODEL #error Sorry, MODEL Not Defined // Surprise, this error is never reached (MODEL was defined by undefined symbol!) #endif #ifdef MODEL_B #error Sorry, MODEL_B Not Defined // This error is reached #endif 

Se DO_THIS è la tua definizione allora la soluzione semplice e funzionante sembra essere l’uso di Macro simile a una funzione:

 #define DO_THIS() 1 #if DO_THIS() //stuff #endif 

Ho provato questo sotto Visual Studio 2008, 2015 e GCC v7.1.1. Se DO_THIS () è indefinito, VS gerera:

avviso C4067: token imprevisti che seguono la direttiva del preprocessore – si aspettava una nuova riga

e GCC genera

errore: operatore binario mancante prima del token “(”

Il compilatore non ha generato un avviso perché questa è una direttiva per il preprocessore. Viene valutato e risolto prima che il compilatore lo veda.

Se sto pensando a questo correttamente.

Le direttive del preprocessore vengono gestite prima che venga compilato qualsiasi codice sorgente. Durante quella fase (s) di traduzione in cui ciò avviene, tutte le direttive del preprocessore, le macro, ecc. Vengono gestite e quindi viene compilato il codice sorgente effettivo.

Poiché #if viene utilizzato per determinare se X è stato definito ed eseguire un’azione se è stato definito o meno. Il #if nello snippet di codice si compilerebbe senza errori perché non ci sono errori per quanto riguarda il compilatore. È sempre ansible creare un file di intestazione con #defines specifici di cui l’applicazione avrebbe bisogno e quindi includere tale intestazione.