C Language
Typ kval
Sök…
Anmärkningar
Typkvalifikationer är nyckelorden som beskriver ytterligare semantik om en typ. De är en integrerad del av typsignaturer. De kan visas både på den översta nivån i en deklaration (som direkt påverkar identifieraren) eller på undernivåer (endast relevant för pekare, som påverkar de pekade värdena):
Nyckelord | Anmärkningar |
---|---|
const | Förhindrar mutationen av det deklarerade objektet (genom att visas på den översta nivån) eller förhindrar mutationen av det pekade till-värdet (genom att visas bredvid en pekarsubtyp). |
volatile | Informerar kompilatorn att det deklarerade objektet (på högsta nivå) eller det pekade värdet (i pekareundertyper) kan ändra dess värde som ett resultat av yttre förhållanden, inte bara som ett resultat av programstyrningsflödet. |
restrict | En optimeringstips, endast relevant för pekare. Förklarar avsikt att inga pekare under pekarens livstid kommer att användas för att komma åt samma pekade objekt. |
Beställningen av typkvalifikatorer med avseende på lagringsklassspecifikationer ( static
, extern
, auto
, register
), typmodifierare ( signed
, unsigned
, short
, long
) och typspecifikatorer ( int
, char
, double
, etc.) verkställs inte, men god praxis är att sätta dem i nämnda ordning:
static const volatile unsigned long int a = 5; /* good practice */
unsigned volatile long static int const b = 5; /* bad practice */
Högsta kvalifikationer
/* "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;
Pekare undertyp kvalifikationer
/* "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;
Omodifierbara (const) variabler
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 */
const
innebär bara att vi inte har rätt att ändra uppgifterna. Det betyder inte att värdet inte kan förändras bakom ryggen.
_Bool doIt(double const* a) {
double rememberA = *a;
// do something long and complicated that calls other functions
return rememberA == *a;
}
Under exekveringen av de andra samtal *a
kan det hända att *a
har ändrats, och därför kan den här funktionen returnera antingen false
eller true
.
Varning
Variabler med const
kvalifikation kan fortfarande ändras med hjälp av pekare:
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" */
Men att göra det är ett fel som leder till odefinierat beteende. Svårigheten här är att detta kan uppträda som förväntat i enkla exempel som detta, men sedan gå fel när koden växer.
Flyktiga variabler
Det volatile
nyckelordet berättar kompilatorn att värdet på variabeln kan ändras när som helst på grund av yttre förhållanden, inte bara som ett resultat av programstyrningsflödet.
Kompilatorn optimerar inte något som har att göra med den flyktiga variabeln.
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;
Det finns två huvudsakliga skäl att använda flyktiga variabler:
- För att gränssnitt med hårdvara som har minnekartade I / O-register.
- När du använder variabler som modifieras utanför programstyrningsflödet (t.ex. i en avbrottstjänstrutin)
Låt oss se detta exempel:
int quit = false;
void main()
{
...
while (!quit) {
// Do something that does not modify the quit variable
}
...
}
void interrupt_handler(void)
{
quit = true;
}
Kompilatorn får lägga märke till medan loopen inte modifierar quit
och konverterar slingan till en oändlig while (true)
slinga. Även om quit
är inställd på signalhanteraren för SIGINT
och SIGTERM
, vet kompilatorn inte det.
Om du quit
som volatile
berättar kompilatorn att inte optimera slingan och problemet kommer att lösas.
Samma problem händer när du använder hårdvara, som vi ser i detta exempel:
uint8_t * pReg = (uint8_t *) 0x1717;
// Wait for register to become non-zero
while (*pReg == 0) { } // Do something else
Optimisators beteende är att läsa variabelns värde en gång, det finns inget behov att läsa om den, eftersom värdet alltid kommer att vara detsamma. Så vi slutar med en oändlig slinga. För att tvinga kompilatorn att göra vad vi vill ändrar vi deklarationen till:
uint8_t volatile * pReg = (uint8_t volatile *) 0x1717;