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;


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow