C Language
Wpisz Kwalifikatory
Szukaj…
Uwagi
Kwalifikatory typu to słowa kluczowe opisujące dodatkową semantykę dotyczącą typu. Są integralną częścią podpisów typu. Mogą pojawiać się zarówno na najwyższym poziomie deklaracji (bezpośrednio wpływając na identyfikator), jak i na poziomach niższych (dotyczy tylko wskaźników, wpływających na wskazane wartości):
Słowo kluczowe | Uwagi |
---|---|
const | Zapobiega mutacji zadeklarowanego obiektu (pojawiając się na najwyższym poziomie) lub zapobiega mutacji wskazanej wartości (pojawiając się obok podtypu wskaźnika). |
volatile | Informuje kompilator, że zadeklarowany obiekt (na najwyższym poziomie) lub wartość wskazywana (w podtypach wskaźnika) może zmienić swoją wartość w wyniku warunków zewnętrznych, nie tylko w wyniku przepływu sterowania programem. |
restrict | Wskazówka dotycząca optymalizacji, dotyczy tylko wskaźników. Deklaruje zamiar, aby przez cały okres użytkowania wskaźnika żadne inne wskaźniki nie były używane w celu uzyskania dostępu do tego samego wskazanego obiektu. |
Kolejność kwalifikatorów typów w odniesieniu do specyfikatorów klas pamięci ( static
, extern
, auto
, register
), modyfikatorów typów ( signed
, unsigned
, short
, long
) i specyfikatorów typów ( int
, char
, double
itp.) Nie jest wymuszona, ale dobrą praktyką jest ułożenie ich w wyżej wymienionej kolejności:
static const volatile unsigned long int a = 5; /* good practice */
unsigned volatile long static int const b = 5; /* bad practice */
Kwalifikacje na najwyższym poziomie
/* "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;
Kwalifikacje podtypu wskaźnika
/* "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;
Zmienne niemodyfikowalne (stałe)
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 */
Kwalifikacja const
oznacza tylko, że nie mamy prawa zmieniać danych. Nie oznacza to, że wartość nie może się zmienić za naszymi plecami.
_Bool doIt(double const* a) {
double rememberA = *a;
// do something long and complicated that calls other functions
return rememberA == *a;
}
Podczas wykonywania innych wywołań *a
mogło ulec zmianie, a zatem ta funkcja może zwrócić wartość false
lub true
.
Ostrzeżenie
Zmienne o const
kwalifikacji można nadal zmieniać za pomocą wskaźników:
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" */
Jest to jednak błąd, który prowadzi do nieokreślonego zachowania. Trudność polega na tym, że w prostych przykładach może to zachowywać się tak, jak się spodziewano, ale potem może pójść źle, gdy kod rośnie.
Zmienne zmienne
volatile
kluczowe informuje kompilator, że wartość zmiennej może się zmienić w każdej chwili w wyniku warunków zewnętrznych, nie tylko w wyniku przepływu sterowania programu.
Kompilator nie zoptymalizuje niczego, co ma związek ze zmienną zmienną.
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;
Istnieją dwa główne powody, dla których należy stosować zmienne zmienne:
- Do połączenia ze sprzętem, który ma rejestry we / wy odwzorowane w pamięci.
- Podczas korzystania ze zmiennych, które są modyfikowane poza przepływem sterowania programem (np. W procedurze obsługi przerwań)
Zobaczmy ten przykład:
int quit = false;
void main()
{
...
while (!quit) {
// Do something that does not modify the quit variable
}
...
}
void interrupt_handler(void)
{
quit = true;
}
Kompilator może zauważyć, że pętla while nie modyfikuje zmiennej quit
i przekształca ją w pętlę while (true)
. Nawet jeśli zmienna quit
została ustawiona w module obsługi sygnałów dla SIGINT
i SIGTERM
, kompilator nie wie o tym.
Uznanie quit
za volatile
spowoduje, że kompilator nie będzie optymalizował pętli, a problem zostanie rozwiązany.
Ten sam problem występuje podczas uzyskiwania dostępu do sprzętu, jak widzimy w tym przykładzie:
uint8_t * pReg = (uint8_t *) 0x1717;
// Wait for register to become non-zero
while (*pReg == 0) { } // Do something else
Optymalizacja polega na tym, że raz odczytuje wartość zmiennej, nie ma potrzeby jej ponownego odczytu, ponieważ wartość zawsze będzie taka sama. W efekcie powstaje nieskończona pętla. Aby zmusić kompilator do robienia tego, co chcemy, modyfikujemy deklarację:
uint8_t volatile * pReg = (uint8_t volatile *) 0x1717;