C Language
Qualificatifs de type
Recherche…
Remarques
Les qualificatifs de type sont les mots-clés décrivant la sémantique supplémentaire d'un type. Ils font partie intégrante des signatures de type. Ils peuvent apparaître à la fois au plus haut niveau d'une déclaration (affectant directement l'identifiant) ou à des sous-niveaux (pertinents uniquement pour les pointeurs, affectant les valeurs pointées):
Mot-clé | Remarques |
---|---|
const | Empêche la mutation de l'objet déclaré (en apparaissant au plus haut niveau) ou empêche la mutation de la valeur pointée (en apparaissant à côté d'un sous-type de pointeur). |
volatile | Informe le compilateur que l'objet déclaré (au plus haut niveau) ou la valeur pointée (dans les sous-types de pointeurs) peut changer sa valeur en raison de conditions externes, et pas uniquement en raison du flux de contrôle du programme. |
restrict | Un indice d'optimisation, uniquement pour les pointeurs. Déclare l'intention que pour la durée de vie du pointeur, aucun autre pointeur ne sera utilisé pour accéder au même objet pointé. |
L'ordre des qualificateurs de type en ce qui concerne les spécificateurs de classe de stockage ( static
, extern
, auto
, register
), les modificateurs de type ( signed
, unsigned
, short
, long
) et les spécificateurs de type ( int
, char
, double
, etc.) la bonne pratique est de les mettre dans l’ordre susmentionné:
static const volatile unsigned long int a = 5; /* good practice */
unsigned volatile long static int const b = 5; /* bad practice */
Qualifications de haut niveau
/* "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;
Qualifications du sous-type de pointeur
/* "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;
Variables non modifiables (const)
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 qualification const
ne signifie que nous n'avons pas le droit de modifier les données. Cela ne signifie pas que la valeur ne peut pas changer dans notre dos.
_Bool doIt(double const* a) {
double rememberA = *a;
// do something long and complicated that calls other functions
return rememberA == *a;
}
Pendant l'exécution des autres appels, *a
peut-être changé, et cette fonction peut donc retourner soit false
soit true
.
Attention
Les variables avec qualification de const
peuvent toujours être modifiées à l'aide de pointeurs:
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" */
Mais le faire est une erreur qui conduit à un comportement indéfini. La difficulté ici est que cela peut se comporter comme prévu dans des exemples simples comme cela, mais ensuite se tromper lorsque le code se développe.
Variables volatiles
Le mot-clé volatile
indique au compilateur que la valeur de la variable peut changer à tout moment en raison de conditions externes, et pas uniquement en raison du flux de contrôle du programme.
Le compilateur n'optimisera rien de la variable 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;
Il y a deux raisons principales pour utiliser des variables volatiles:
- Pour interfacer avec le matériel qui a des registres d'E / S mappés en mémoire.
- Lors de l'utilisation de variables modifiées en dehors du flux de contrôle du programme (par exemple, dans une routine de service d'interruption)
Voyons cet exemple:
int quit = false;
void main()
{
...
while (!quit) {
// Do something that does not modify the quit variable
}
...
}
void interrupt_handler(void)
{
quit = true;
}
Le compilateur est autorisé à remarquer que la boucle while ne modifie pas la variable de quit
et convertit la boucle en une boucle while (true)
sans fin. Même si la variable quit
est définie sur le gestionnaire de signaux pour SIGINT
et SIGTERM
, le compilateur ne le sait pas.
Déclarer quit
as volatile
va dire au compilateur de ne pas optimiser la boucle et le problème sera résolu.
Le même problème se produit lors de l'accès au matériel, comme nous le voyons dans cet exemple:
uint8_t * pReg = (uint8_t *) 0x1717;
// Wait for register to become non-zero
while (*pReg == 0) { } // Do something else
Le comportement de l'optimiseur est de lire la valeur de la variable une fois, il n'est pas nécessaire de la relire, car la valeur sera toujours la même. On se retrouve donc avec une boucle infinie. Pour forcer le compilateur à faire ce que nous voulons, nous modifions la déclaration pour:
uint8_t volatile * pReg = (uint8_t volatile *) 0x1717;