C Language
Type kwalificaties
Zoeken…
Opmerkingen
Type qualifiers zijn de sleutelwoorden die extra semantiek over een type beschrijven. Ze vormen een integraal onderdeel van typeaanduidingen. Ze kunnen zowel op het bovenste niveau van een aangifte (direct van invloed op de identifier) of op subniveaus (alleen relevant voor pointers, van invloed op de doorverwijswaarden) voorkomen:
keyword | Opmerkingen |
---|---|
const | Voorkomt de mutatie van het gedeclareerde object (door op het bovenste niveau te verschijnen) of voorkomt de mutatie van de puntwaarde (door naast een pointer-subtype te verschijnen). |
volatile | Informeert de compiler dat het gedeclareerde object (op het hoogste niveau) of de puntwaarde (in aanwijzersubtypen) de waarde ervan kan wijzigen als gevolg van externe omstandigheden, niet alleen als gevolg van de programmabesturingsstroom. |
restrict | Een optimalisatietip, alleen relevant voor pointers. Verklaart de bedoeling dat gedurende de levensduur van de aanwijzer geen andere aanwijzers zullen worden gebruikt om toegang te krijgen tot hetzelfde puntige object. |
De volgorde van typekwalificaties met betrekking tot opslagklasse-specificatie ( static
, extern
, auto
, register
), type-modificatie ( signed
, unsigned
, short
, long
) en type-specificatie ( int
, char
, double
, etc.) wordt niet afgedwongen, maar het is een goede gewoonte om ze in de bovengenoemde volgorde te plaatsen:
static const volatile unsigned long int a = 5; /* good practice */
unsigned volatile long static int const b = 5; /* bad practice */
Kwalificaties op het hoogste 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;
Kwalificaties subtype aanwijzer
/* "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;
Onaanpasbare (const) variabelen
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 */
De const
kwalificatie betekent alleen dat we niet het recht hebben om de gegevens te wijzigen. Het betekent niet dat de waarde niet achter onze rug kan veranderen.
_Bool doIt(double const* a) {
double rememberA = *a;
// do something long and complicated that calls other functions
return rememberA == *a;
}
Tijdens de uitvoering van de andere aanroepen *a
kan er *a
zijn gewijzigd, en daarom kan deze functie false
of true
teruggeven.
Waarschuwing
Variabelen met const
kwalificatie kunnen nog steeds worden gewijzigd met behulp van pointers:
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" */
Maar dit is een fout die leidt tot ongedefinieerd gedrag. De moeilijkheid hier is dat dit zich in eenvoudige voorbeelden als dit kan gedragen, maar dan mis kan gaan wanneer de code groeit.
Vluchtige variabelen
Het volatile
sleutelwoord vertelt de compiler dat de waarde van de variabele op elk moment kan veranderen als gevolg van externe omstandigheden, niet alleen als gevolg van de programmastroom.
De compiler zal niets optimaliseren dat te maken heeft met de vluchtige variabele.
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;
Er zijn twee hoofdredenen om vluchtige variabelen te gebruiken:
- Om te communiceren met hardware met geheugen toegewezen I / O-registers.
- Bij gebruik van variabelen die buiten de programmabesturingsstroom zijn gewijzigd (bijvoorbeeld in een onderbrekingsserviceroutine)
Laten we dit voorbeeld bekijken:
int quit = false;
void main()
{
...
while (!quit) {
// Do something that does not modify the quit variable
}
...
}
void interrupt_handler(void)
{
quit = true;
}
De compiler mag opmerken dat de while-lus de variabele quit
niet wijzigt en de lus omzet in een eindeloze while (true)
lus while (true)
. Zelfs als de variabele quit
is ingesteld op de signaalhandler voor SIGINT
en SIGTERM
, weet de compiler dat niet.
Als quit
als volatile
wordt verklaard, vertelt de compiler dat de lus niet moet worden geoptimaliseerd en wordt het probleem opgelost.
Hetzelfde probleem treedt op bij toegang tot hardware, zoals we in dit voorbeeld zien:
uint8_t * pReg = (uint8_t *) 0x1717;
// Wait for register to become non-zero
while (*pReg == 0) { } // Do something else
Het gedrag van de optimizer is om de waarde van de variabele eenmaal te lezen, het is niet nodig om deze opnieuw te lezen, omdat de waarde altijd hetzelfde zal zijn. Dus we eindigen met een oneindige lus. Om de compiler te dwingen te doen wat we willen, passen we de verklaring aan om:
uint8_t volatile * pReg = (uint8_t volatile *) 0x1717;