“Const statico” vs “#define” vs “enum”

Quale è meglio usare tra le dichiarazioni sottostanti in C?

static const int var = 5; 

o

 #define var 5 

o

 enum { var = 5 }; 

Parlando in generale:

 static const 

Perché rispetta lo scopo ed è sicuro dal punto di vista del tipo.

L’unica avvertenza che ho potuto vedere: se vuoi che la variabile sia definita sulla riga di comando. C’è ancora un’alternativa:

 #ifdef VAR // Very bad name, not long enough, too general, etc.. static int const var = VAR; #else static int const var = 5; // default value #endif 

Quando ansible, invece di macro / ellissi, utilizzare un’alternativa sicura al tipo.

Se hai davvero bisogno di andare con una macro (per esempio, vuoi __FILE__ o __LINE__ ), allora è meglio dare un nome alla tua macro MOLTO attentamente: nella sua convenzione di denominazione Boost raccomanda tutto il maiuscolo, iniziando dal nome del progetto ( qui BOOST_), mentre si esamina la libreria si noterà che questo è (generalmente) seguito dal nome della particolare area (libreria) quindi con un nome significativo.

Generalmente significa nomi lunghi 🙂

Dipende da cosa ti serve il valore. Tu (e tutti gli altri finora) hai omesso la terza alternativa:

  1. static const int var = 5;
  2. #define var 5
  3. enum { var = 5 };

Ignorando i problemi relativi alla scelta del nome, quindi:

  • Se è necessario passare un puntatore, è necessario utilizzare (1).
  • Poiché (2) è apparentemente un’opzione, non è necessario passare i puntatori.
  • Entrambi (1) e (3) hanno un simbolo nella tabella dei simboli del debugger, che semplifica il debugging. È più probabile che (2) non abbia un simbolo, lasciandoti meravigliare di cosa si tratta.
  • (1) non può essere utilizzato come dimensione per gli array a livello globale; entrambi (2) e (3) possono.
  • (1) non può essere utilizzato come dimensione per gli array statici nell’ambito della funzione; entrambi (2) e (3) possono.
  • Sotto C99, tutti questi possono essere utilizzati per gli array locali. Tecnicamente, usare (1) implicherebbe l’uso di un VLA (array a lunghezza variabile), sebbene la dimensione referenziata da ‘var’ sarebbe naturalmente fissata alla dimensione 5.
  • (1) non può essere utilizzato in luoghi come le dichiarazioni di switch; entrambi (2) e (3) possono.
  • (1) non può essere utilizzato per inizializzare variabili statiche; entrambi (2) e (3) possono.
  • (2) può modificare il codice che non si desidera modificare perché viene utilizzato dal preprocessore; entrambi (1) e (3) non avranno effetti collaterali inaspettati come questo.
  • È ansible rilevare se (2) è stato impostato nel preprocessore; né (1) né (3) lo consente.

Quindi, nella maggior parte dei contesti, preferisci l’enum alle alternative. Altrimenti, il primo e l’ultimo punto elenco possono essere i fattori di controllo e devi pensare più duramente se devi soddisfare entrambi contemporaneamente.

Se stavi chiedendo di C ++, allora dovresti usare l’opzione (1) – la costante statica – ogni volta.

In C, in particolare? In C la risposta corretta è: usa #define (o, se appropriato, enum )

Mentre è utile avere le proprietà di scoping e typing di un object const , in realtà gli oggetti const in C (al contrario di C ++) non sono costanti reali e quindi sono solitamente inutili nella maggior parte dei casi pratici.

Quindi, in C, la scelta dovrebbe essere determinata dal modo in cui prevedi di utilizzare la tua costante. Ad esempio, non è ansible utilizzare un object const int come etichetta del case (mentre una macro funzionerà). Non è ansible utilizzare un object const int come larghezza del campo di bit (mentre una macro funzionerà). In C89 / 90 non è ansible utilizzare un object const per specificare una dimensione di matrice (mentre una macro funzionerà). Anche in C99 non è ansible utilizzare un object const per specificare una dimensione di matrice quando è necessario un array non VLA .

Se questo è importante per te, determinerà la tua scelta. Il più delle volte, non avrai altra scelta che usare #define in C. E non dimenticare un’altra alternativa, che produce costanti vere in C- enum .

In C ++ gli oggetti const sono vere costanti, quindi in C ++ è quasi sempre preferibile la variante const (non c’è bisogno di esplicita static in C ++).

La differenza tra static const e #define è che il primo utilizza la memoria e l’ultimo non usa la memoria per l’archiviazione. In secondo luogo, non è ansible passare l’indirizzo di un #define mentre è ansible passare l’indirizzo di un static const . In realtà dipende da quale circostanza siamo sotto, dobbiamo selezionare uno di questi due. Entrambi sono al loro meglio in diverse circostanze. Per favore non dare per scontato che uno sia migliore dell’altro … 🙂

Se fosse stato così, Dennis Ritchie avrebbe tenuto il migliore da solo … hahaha … 🙂

In C #define è molto più popolare. È ansible utilizzare questi valori per dichiarare le dimensioni degli array, ad esempio:

 #define MAXLEN 5 void foo(void) { int bar[MAXLEN]; } 

ANSI C non ti permette di usare static const in questo contesto, per quanto ne so. In C ++ dovresti evitare le macro in questi casi. Tu puoi scrivere

 const int maxlen = 5; void foo() { int bar[maxlen]; } 

e persino omettere static perché il collegamento interno è implicito già da const [solo in C ++].

Un altro svantaggio di const in C è che non è ansible utilizzare il valore nell’inizializzazione di un altro const .

 static int const NUMBER_OF_FINGERS_PER_HAND = 5; static int const NUMBER_OF_HANDS = 2; // initializer element is not constant, this does not work. static int const NUMBER_OF_FINGERS = NUMBER_OF_FINGERS_PER_HAND * NUMBER_OF_HANDS; 

Anche questo non funziona con un const dal momento che il compilatore non lo vede come una costante:

 static uint8_t const ARRAY_SIZE = 16; static int8_t const lookup_table[ARRAY_SIZE] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; // ARRAY_SIZE not a constant! 

Sarei felice di usare const digitato in questi casi, altrimenti …

Se riesci a farla franca, static const ha molti vantaggi. Rispetta i normali principi dell’ambito, è visibile in un debugger e in generale obbedisce alle regole a cui le variabili obbediscono.

Tuttavia, almeno nello standard C originale, non è in realtà una costante. Se usi #define var 5 , puoi scrivere int foo[var]; come dichiarazione, ma non puoi farlo (eccetto come estensione del compilatore “con static const int var = 5; Questo non è il caso in C ++, dove la versione static const può essere usata ovunque la versione #define può, e credo che questo sia anche il caso del C99.

Tuttavia, non nominare mai una costante #define con un nome in minuscolo. Sostituirà ogni ansible uso di quel nome fino alla fine dell’unità di traduzione. Le costanti macro dovrebbero trovarsi in quello che è effettivamente il loro spazio dei nomi, che è tradizionalmente tutte lettere maiuscole, forse con un prefisso.

#define var 5 ti causerà problemi se hai cose come mystruct.var .

Per esempio,

 struct mystruct { int var; }; #define var 5 int main() { struct mystruct foo; foo.var = 1; return 0; } 

Il preprocessore lo sostituirà e il codice non verrà compilato. Per questo motivo, lo stile di codifica tradizionale suggerisce tutte le costanti #define utilizza lettere maiuscole per evitare conflitti.

Ho scritto un programma di test rapido per dimostrare una differenza:

 #include  enum {ENUM_DEFINED=16}; enum {ENUM_DEFINED=32}; #define DEFINED_DEFINED 16 #define DEFINED_DEFINED 32 int main(int argc, char *argv[]) { printf("%d, %d\n", DEFINED_DEFINED, ENUM_DEFINED); return(0); } 

Questo compila con questi errori e avvertenze:

 main.c:6:7: error: redefinition of enumerator 'ENUM_DEFINED' enum {ENUM_DEFINED=32}; ^ main.c:5:7: note: previous definition is here enum {ENUM_DEFINED=16}; ^ main.c:9:9: warning: 'DEFINED_DEFINED' macro redefined [-Wmacro-redefined] #define DEFINED_DEFINED 32 ^ main.c:8:9: note: previous definition is here #define DEFINED_DEFINED 16 ^ 

Si noti che enum dà un errore quando define fornisce un avvertimento.

La definizione

 const int const_value = 5; 

non sempre definisce un valore costante. Alcuni compilatori (ad esempio tcc 0.9.26 ) assegnano solo memoria identificata con il nome “const_value”. Usando l’identificatore “const_value” non puoi modificare questa memoria. Ma è ancora ansible modificare la memoria utilizzando un altro identificatore:

 const int const_value = 5; int *mutable_value = (int*) &const_value; *mutable_value = 3; printf("%i", const_value); // The output may be 5 or 3, depending on the compiler. 

Questo significa la definizione

 #define CONST_VALUE 5 

è l’unico modo per definire un valore costante che non può essere modificato in alcun modo.

È SEMPRE preferibile utilizzare const, anziché #define. Questo perché const è trattato dal compilatore e #define dal preprocessore. È come se #define non faccia parte del codice (in parole povere).

Esempio:

 #define PI 3.1416 

Il nome simbolico PI potrebbe non essere mai visto dai compilatori; può essere rimosso dal preprocessore prima che il codice sorgente arrivi anche a un compilatore. Di conseguenza, il nome PI potrebbe non essere inserito nella tabella dei simboli. Ciò può creare confusione se si verifica un errore durante la compilazione che implica l’utilizzo della costante, poiché il messaggio di errore può fare riferimento a 3.1416, non a PI. Se PI fosse definito in un file di intestazione che non hai scritto, non avresti idea da dove provenga quel 3.1416.

Questo problema può anche apparire in un debugger simbolico, perché, di nuovo, il nome con cui stai programmando potrebbe non essere nella tabella dei simboli.

Soluzione:

 const double PI = 3.1416; //or static const... 

Non pensare che ci sia una risposta per “che è sempre il migliore” ma, come diceva Matthieu

static const

è sicuro. Il mio più grande problema con #define , però, è quando esegui il debug in Visual Studio non puoi guardare la variabile. Dà un errore che il simbolo non può essere trovato.

Per inciso, un’alternativa a #define , che fornisce una scoping adeguata ma si comporta come una costante “reale”, è “enum”. Per esempio:

 enum {number_ten = 10;} 

In molti casi, è utile definire tipi enumerati e creare variabili di questi tipi; se ciò è fatto, i debugger potrebbero essere in grado di visualizzare variabili in base al loro nome di enumerazione.

Un importante avvertimento per farlo, tuttavia: in C ++, i tipi enumerati hanno una compatibilità limitata con gli interi. Ad esempio, per impostazione predefinita, non è ansible eseguire operazioni aritmetiche su di essi. Trovo che sia un curioso comportamento predefinito per l’enumerazione; mentre sarebbe stato bello avere un tipo “enum rigoroso”, dato il desiderio di avere C ++ generalmente compatibile con C, penserei che il comportamento predefinito di un tipo “enum” debba essere intercambiabile con gli interi.

Sebbene la domanda riguardasse gli interi, vale la pena notare che #define ed enumerazioni sono inutili se hai bisogno di una struttura o di una stringa costante. Questi sono generalmente passati a funzioni come indicatori. (Con le stringhe è necessario, con le strutture è molto più efficiente).

Per quanto riguarda gli interi, se ci si trova in un ambiente embedded con memoria molto limitata, potrebbe essere necessario preoccuparsi di dove viene memorizzata la costante e in che modo vengono compilati gli accessi. Il compilatore potrebbe aggiungere due consts in fase di esecuzione, ma aggiungere due #definiti in fase di compilazione. Una costante #define può essere convertita in una o più istruzioni MOV [immediate], il che significa che la costante viene effettivamente memorizzata nella memoria del programma. Una costante const sarà memorizzata nella sezione .const nella memoria dati. Nei sistemi con un’architettura di Harvard, potrebbero esserci differenze nelle prestazioni e nell’uso della memoria, anche se probabilmente sarebbero piccole. Potrebbero essere importanti per l’ottimizzazione hard-core dei loop interni.

Una semplice differenza:

Al momento del preelaborazione, la costante viene sostituita con il suo valore. Pertanto non è ansible applicare l’operatore di dereferenziazione a una definizione, ma è ansible applicare l’operatore di dereferenziazione a una variabile.

Come supponete, definire è più veloce di const statico.

Ad esempio, avendo:

 #define mymax 100 

non puoi fare printf("address of constant is %p",&mymax); .

Ma avendo

 const int mymax_var=100 

puoi fare printf("address of constant is %p",&mymax_var); .

Per essere più chiari, la definizione viene sostituita dal suo valore nella fase di pre-elaborazione, quindi non abbiamo alcuna variabile memorizzata nel programma. Abbiamo solo il codice dal segmento di testo del programma in cui è stata utilizzata la definizione.

Tuttavia, per const statico abbiamo una variabile allocata da qualche parte. Per gcc, const statici sono allocati nel segmento di testo del programma.

Sopra, volevo parlare dell’operatore di riferimento in modo da sostituire la dereferenziazione con riferimento.

Abbiamo esaminato il codice assembler prodotto sul MBF16X … Entrambe le varianti producono lo stesso codice per le operazioni aritmetiche (ADD Immediate, ad esempio).

Quindi const int è preferito per il controllo del tipo mentre #define è vecchio stile. Forse è specifico del compilatore. Quindi controlla il codice assembler prodotto.