C Language
Tipo qualificatori
Ricerca…
Osservazioni
I qualificatori di tipo sono le parole chiave che descrivono una semantica aggiuntiva su un tipo. Sono parte integrante delle firme dei tipi. Possono apparire sia al livello più alto di una dichiarazione (che influisce direttamente sull'identificatore) sia a sottolivelli (rilevanti solo per i puntatori che influiscono sui valori puntati):
Parola chiave | Osservazioni |
---|---|
const | Impedisce la mutazione dell'oggetto dichiarato (apparendo al livello più alto) o impedisce la mutazione del valore puntato (apparendo accanto a un sottotipo di puntatore). |
volatile | Informa il compilatore che l'oggetto dichiarato (al livello più alto) o il valore puntato (nei sottotipi del puntatore) può cambiare il suo valore come risultato di condizioni esterne, non solo come risultato del flusso di controllo del programma. |
restrict | Un suggerimento per l'ottimizzazione, pertinente solo ai puntatori. Dichiara l'intento che per tutta la durata del puntatore, non verranno utilizzati altri puntatori per accedere allo stesso oggetto puntato. |
L'ordinamento di tipo qualificatori rispetto a specificatori della classe di memorizzazione ( static
, extern
, auto
, register
), tipo di modificatori ( signed
, unsigned
, short
, long
) e specificatori di tipo ( int
, char
, double
, ecc) non è applicata, ma la buona pratica è metterli nell'ordine di cui sopra:
static const volatile unsigned long int a = 5; /* good practice */
unsigned volatile long static int const b = 5; /* bad practice */
Qualifiche di alto livello
/* "a" cannot be mutated by the program but can change as a result of external conditions */
const volatile int a = 5;
/* the const applies to array elements, i.e. "a[0]" cannot be mutated */
const int arr[] = { 1, 2, 3 };
/* for the lifetime of "ptr", no other pointer could point to the same "int" object */
int *restrict ptr;
Qualifiche del sottotipo di puntatore
/* "s1" can be mutated, but "*s1" cannot */
const char *s1 = "Hello";
/* neither "s2" (because of top-level const) nor "*s2" can be mutated */
const char *const s2 = "World";
/* "*p" may change its value as a result of external conditions, "**p" and "p" cannot */
char *volatile *p;
/* "q", "*q" and "**q" may change their values as a result of external conditions */
volatile char *volatile *volatile q;
Variabili (const) non modificabili
const int a = 0; /* This variable is "unmodifiable", the compiler
should throw an error when this variable is changed */
int b = 0; /* This variable is modifiable */
b += 10; /* Changes the value of 'b' */
a += 10; /* Throws a compiler error */
La qualifica const
significa solo che non abbiamo il diritto di modificare i dati. Ciò non significa che il valore non può cambiare alle nostre spalle.
_Bool doIt(double const* a) {
double rememberA = *a;
// do something long and complicated that calls other functions
return rememberA == *a;
}
Durante l'esecuzione delle altre chiamate *a
potrebbe essersi verificato un cambiamento, e quindi questa funzione potrebbe tornare false
o true
.
avvertimento
Le variabili con qualifica const
possono essere modificate usando i puntatori:
const int a = 0;
int *a_ptr = (int*)&a; /* This conversion must be explicitly done with a cast */
*a_ptr += 10; /* This has undefined behavior */
printf("a = %d\n", a); /* May print: "a = 10" */
Ma farlo è un errore che porta a comportamenti non definiti. La difficoltà qui è che questo può comportarsi come previsto in semplici esempi come questo, ma poi andare male quando il codice cresce.
Variabili volatili
La parola chiave volatile
indica al compilatore che il valore della variabile può cambiare in qualsiasi momento come risultato di condizioni esterne, non solo come risultato del flusso di controllo del programma.
Il compilatore non ottimizzerà tutto ciò che ha a che fare con la variabile volatile.
volatile int foo; /* Different ways to declare a volatile variable */
int volatile foo;
volatile uint8_t * pReg; /* Pointers to volatile variable */
uint8_t volatile * pReg;
Ci sono due ragioni principali per utilizzare variabili volatili:
- Interfaccia con hardware con registri I / O mappati in memoria.
- Quando si utilizzano variabili che vengono modificate al di fuori del flusso di controllo del programma (ad esempio, in una routine di servizio di interrupt)
Vediamo questo esempio:
int quit = false;
void main()
{
...
while (!quit) {
// Do something that does not modify the quit variable
}
...
}
void interrupt_handler(void)
{
quit = true;
}
Il compilatore può notare che il ciclo while non modifica la variabile quit
e converte il ciclo in un ciclo while (true)
senza fine. Anche se la variabile quit
è impostata sul gestore di segnale per SIGINT
e SIGTERM
, il compilatore non lo sa.
Dichiarare quit
come volatile
dirà al compilatore di non ottimizzare il ciclo e il problema sarà risolto.
Lo stesso problema si verifica quando si accede all'hardware, come vediamo in questo esempio:
uint8_t * pReg = (uint8_t *) 0x1717;
// Wait for register to become non-zero
while (*pReg == 0) { } // Do something else
Il comportamento dell'ottimizzatore è di leggere il valore della variabile una volta, non c'è bisogno di rileggerlo, poiché il valore sarà sempre lo stesso. Quindi finiamo con un ciclo infinito. Per forzare il compilatore a fare ciò che vogliamo, modifichiamo la dichiarazione in:
uint8_t volatile * pReg = (uint8_t volatile *) 0x1717;