Durante la revisione del codice, applico le seguenti regole:
-
Usa sempre const
per i parametri di funzione passati per riferimento dove il
la funzione non modifica (o libera) i dati puntati a.
int find(const int *data, size_t size, int value);
-
Usa sempre const
per le costanti che potrebbero altrimenti essere definite usando un #define o un enum. Il compilatore può trovare i dati nella memoria di sola lettura (ROM) come risultato (sebbene il linker sia spesso uno strumento migliore per questo scopo nei sistemi embedded).
const double PI = 3.14;
-
Non utilizzare mai const in una funzione prototype per un parametro passato
Valore . Non ha significato ed è quindi solo 'rumore'.
// don't add const to 'value' or 'size'
int find(const int *data, size_t size, const int value);
-
Se appropriato, utilizza const volatile
in posizioni che non possono essere modificate dal programma ma potrebbero comunque cambiare. I registri hardware sono il caso d'uso tipico qui, ad esempio un registro di stato che riflette uno stato del dispositivo:
const volatile int32_t *DEVICE_STATUS = (int32_t*) 0x100;
Altri usi sono opzionali. Ad esempio, i parametri di una funzione all'interno della funzione implementazione possono essere contrassegnati come const.
// 'value' and 'size can be marked as const here
int find(const int *data, const size_t size, const int value)
{
... etc
o funzione restituisce valori o calcoli che si ottengono e non cambiano mai:
char *repeat_str(const char *str, size_t n)
{
const size_t len = strlen(str);
const size_t buf_size = 1 + (len * n);
char *buf = malloc(buf_size);
...
Questi usi di const
indicano semplicemente che non cambierai la variabile; non cambiano come o dove è memorizzata la variabile. Il compilatore può ovviamente capire che una variabile non è cambiata, ma aggiungendo const
gli permetti di farla rispettare. Questo può aiutare il lettore e aggiungere sicurezza (anche se le tue funzioni sono grandi o
abbastanza complicato che questo faccia una grande differenza, probabilmente ne hai altri
i problemi). Modifica - es. una funzione a 200 righe con codifica densa con loop nidificati e molti lunghi
o nomi di variabili simili, sapendo che alcune variabili non possono cambiare
facilità sottolineando in modo significativo. Tali funzioni sono state mal progettate o
maintened.
Problemi con const
. Probabilmente sentirai il termine "avvelenamento da cost".
Ciò si verifica quando l'aggiunta di const
a un parametro di funzione fa sì che 'constness'
propagarsi.
Modifica - avvelenamento const: ad esempio nella funzione:
int function_a(char * str, int n)
{
...
function_b(str);
...
}
se cambiamo str
in const
, dobbiamo quindi assicurarci che anche fuction_b
tenga
a const
. E così via se function_b
passa str
a function_c
,
ecc. Come puoi immaginare questo potrebbe essere doloroso se si propaga in molti
file / moduli separati. Se si propaga in una funzione che non può essere
cambiato (es. una libreria di sistema), quindi diventa necessario un cast. Quindi aspersione
const
around nel codice esistente sta forse chiedendo problemi. Nel nuovo codice
tuttavia, è preferibile che const
si adatti coerentemente laddove appropriato.
Il problema più insidioso di const
è che non era nell'originale
linguaggio. Come componente aggiuntivo, non si adatta perfettamente. Per cominciare ha due significati
(come nelle regole precedenti, che significa "Non ho intenzione di cambiare questo" e "questo non può essere modificato"). Ma più di questo, può essere pericoloso. Ad esempio, compilare e
eseguire questo codice e (a seconda del compilatore / opzioni) potrebbe andare in crash quando
eseguire:
const char str[] = "hello world\n";
char *s = strchr(str, '\n');
*s = 'int find(const int *data, size_t size, int value);
';
strchr
restituisce un char*
non un const char*
. Come il suo parametro di chiamata è
const
deve eseguire il cast il parametro call a char*
. E in questo caso quello
getta via la vera proprietà di archiviazione di sola lettura. Modifica: - questo vale in generale per i vars nella memoria di sola lettura. Per "ROM", intendo non solo la ROM fisica ma qualsiasi memoria protetta da scrittura, come accade alla sezione di codice dei programmi eseguiti su un sistema operativo tipico.
Molte funzioni di libreria standard si comportano allo stesso modo, quindi fai attenzione: quando tu
avere costanti reali (ad esempio memorizzate nella ROM), devi stare molto attento a non farlo
perdere la loro costanza.