C Language
Typ Qualifiers
Suche…
Bemerkungen
Typqualifizierer sind die Schlüsselwörter, die zusätzliche Semantik eines Typs beschreiben. Sie sind ein integraler Bestandteil von Typunterschriften. Sie können sowohl auf der obersten Ebene einer Deklaration (direkt auf den Bezeichner) als auch auf Unterebenen (nur für Zeiger, die die Zeigerwerte betreffen) angezeigt werden:
Stichwort | Bemerkungen |
---|---|
const | Verhindert die Mutation des deklarierten Objekts (indem es auf der obersten Ebene angezeigt wird) oder verhindert die Mutation des angegebenen Werts (indem er neben einem Zeigertyp angezeigt wird). |
volatile | Informiert den Compiler, dass das deklarierte Objekt (auf oberster Ebene) oder der angegebene Wert (in Zeigeruntertypen) seinen Wert aufgrund externer Bedingungen ändern kann, nicht nur als Ergebnis eines Programmsteuerungsflusses. |
restrict | Ein Optimierungshinweis, der nur für Zeiger relevant ist. Erklärt die Absicht, dass während der Lebensdauer des Zeigers keine anderen Zeiger verwendet werden, um auf dasselbe Objekt zuzugreifen. |
Die Reihenfolge der Typkennzeichner in Bezug auf Speicherklassenspezifizierer ( static
, extern
, auto
, register
), Typmodifizierer ( signed
, unsigned
, short
, long
) und Typbezeichner ( int
, char
, double
usw.) wird nicht erzwungen, sondern Es ist empfehlenswert, sie in die oben genannte Reihenfolge zu bringen:
static const volatile unsigned long int a = 5; /* good practice */
unsigned volatile long static int const b = 5; /* bad practice */
Qualifikationen auf höchstem 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;
Zeigertyp-Qualifikationen
/* "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;
Unveränderbare (const) Variablen
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 */
Die const
Qualifikation bedeutet nur, dass wir kein Recht haben, die Daten zu ändern. Das bedeutet nicht, dass sich der Wert hinter unserem Rücken nicht ändern kann.
_Bool doIt(double const* a) {
double rememberA = *a;
// do something long and complicated that calls other functions
return rememberA == *a;
}
Während der Ausführung der anderen Aufrufe hat sich *a
möglicherweise geändert, so dass diese Funktion entweder false
oder true
.
Warnung
Variablen mit const
Qualifizierung können noch mit Zeigern geändert werden:
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" */
Dies ist jedoch ein Fehler, der zu undefiniertem Verhalten führt. Die Schwierigkeit hier ist, dass sich dies in einfachen Beispielen wie erwartet verhalten kann, dann aber ein Fehler auftritt, wenn der Code wächst.
Flüchtige Variablen
Das volatile
Schlüsselwort teilt dem Compiler mit, dass sich der Wert der Variablen jederzeit aufgrund von externen Bedingungen ändern kann, nicht nur als Ergebnis eines Programmsteuerungsflusses.
Der Compiler optimiert nichts, was mit der flüchtigen Variablen zu tun hat.
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;
Es gibt zwei Hauptgründe, volatile Variablen zu verwenden:
- Schnittstelle zu Hardware, die E / A-Register mit Speicherzuordnung besitzt.
- Bei Verwendung von Variablen, die außerhalb des Programmsteuerungsflusses geändert werden (z. B. in einer Interrupt-Serviceroutine)
Sehen wir uns dieses Beispiel an:
int quit = false;
void main()
{
...
while (!quit) {
// Do something that does not modify the quit variable
}
...
}
void interrupt_handler(void)
{
quit = true;
}
Der Compiler darf feststellen, dass die while-Schleife die quit
Variable nicht ändert quit
und konvertiert die Schleife in eine endlose while (true)
Schleife while (true)
. Selbst wenn die quit
Variable für den Signalhandler für SIGINT
und SIGTERM
, weiß dies der Compiler nicht.
Wenn Sie quit
als volatile
deklarieren quit
wird der Compiler angewiesen, die Schleife nicht zu optimieren, und das Problem wird gelöst.
Das gleiche Problem tritt beim Zugriff auf Hardware auf, wie wir in diesem Beispiel sehen:
uint8_t * pReg = (uint8_t *) 0x1717;
// Wait for register to become non-zero
while (*pReg == 0) { } // Do something else
Das Verhalten des Optimierers besteht darin, den Wert der Variablen einmal zu lesen. Ein erneutes Lesen ist nicht erforderlich, da der Wert immer derselbe ist. So landen wir mit einer Endlosschleife. Um den Compiler zu zwingen, das zu tun, was wir wollen, ändern wir die Deklaration in:
uint8_t volatile * pReg = (uint8_t volatile *) 0x1717;