C Language
Тип Квалификаторы
Поиск…
замечания
Квалификаторы типов - это ключевые слова, которые описывают дополнительную семантику о типе. Они являются неотъемлемой частью сигнатур типа. Они могут отображаться как на самом верхнем уровне объявления (непосредственно влияющем на идентификатор), так и на под-уровнях (что касается только указателей, влияющих на значения, отмеченные):
Ключевое слово | замечания |
---|---|
const | Предотвращает мутацию объявленного объекта (если он отображается на самом верхнем уровне) или предотвращает мутацию указательного значения (путем появления рядом с подтипом указателя). |
volatile | Сообщает компилятору, что объявленный объект (на самом верхнем уровне) или указательное значение (в подтипах указателя) может изменить свое значение в результате внешних условий, а не только в результате потока управления программой. |
restrict | Рекомендации по оптимизации, относящиеся только к указателям. Объявляет намерение, что для жизни указателя никакие другие указатели не будут использоваться для доступа к одному и тому же объекту с указателем. |
Порядок выполнения спецификаторов типов в отношении спецификаторов класса хранения ( static
, extern
, auto
, register
), модификаторы типов ( signed
, unsigned
, short
, long
) и спецификаторы типов ( int
, char
, double
и т. Д.) Не применяются, но хорошая практика заключается в том, чтобы привести их в вышеупомянутый порядок:
static const volatile unsigned long int a = 5; /* good practice */
unsigned volatile long static int const b = 5; /* bad practice */
Квалификация высшего уровня
/* "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;
Требования к подтипу указателя
/* "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;
Немодифицируемые (const) переменные
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
означает только то, что мы не имеем права изменять данные. Это не означает, что ценность не может измениться за нашей спиной.
_Bool doIt(double const* a) {
double rememberA = *a;
// do something long and complicated that calls other functions
return rememberA == *a;
}
Во время выполнения других вызовов *a
может измениться, и поэтому эта функция может возвращать либо false
либо true
.
Предупреждение
Переменные с const
квалификацией могут быть изменены с помощью указателей:
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" */
Но это ошибка, которая приводит к неопределенному поведению. Трудность здесь состоит в том, что это может вести себя так, как ожидалось, в простых примерах, как это, но затем идти не так, когда код растет.
Неустойчивые переменные
Ключевое слово volatile
сообщает компилятору, что значение переменной может измениться в любое время в результате внешних условий, а не только в результате потока управления программой.
Компилятор не будет оптимизировать что-либо, что связано с изменчивой переменной.
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;
Существует две основные причины использования изменчивых переменных:
- Для взаимодействия с оборудованием, имеющим регистры ввода-вывода с отображением памяти.
- При использовании переменных, которые изменяются вне потока управления программой (например, в процедуре обслуживания прерываний)
Давайте посмотрим на этот пример:
int quit = false;
void main()
{
...
while (!quit) {
// Do something that does not modify the quit variable
}
...
}
void interrupt_handler(void)
{
quit = true;
}
Компилятору разрешено заметить, что цикл while не изменяет переменную quit
и преобразовывает цикл в бесконечный цикл while (true)
. Даже если переменная quit
задана в обработчике сигналов для SIGINT
и SIGTERM
, компилятор этого не знает.
Объявление quit
as volatile
подскажет компилятору не оптимизировать цикл, и проблема будет решена.
Такая же проблема возникает при доступе к аппаратным средствам, как мы видим в этом примере:
uint8_t * pReg = (uint8_t *) 0x1717;
// Wait for register to become non-zero
while (*pReg == 0) { } // Do something else
Поведение оптимизатора состоит в том, чтобы прочитать значение переменной один раз, нет необходимости перечитывать его, так как значение всегда будет одинаковым. Таким образом, мы заканчиваем бесконечным циклом. Чтобы заставить компилятор делать то, что мы хотим, мы модифицируем объявление:
uint8_t volatile * pReg = (uint8_t volatile *) 0x1717;