volatile type qualifier
Каждый отдельный тип в системе типов C
type system
имеет несколько
квалифицированных
версий этого типа, соответствующих одному, двум или всем трём квалификаторам:
const
,
volatile
и, для указателей на объектные типы,
restrict
. На этой странице описываются эффекты квалификатора
volatile
.
Каждое обращение (как чтение, так и запись), выполняемое через lvalue-выражение типа с квалификатором volatile, считается наблюдаемым побочным эффектом для целей оптимизации и вычисляется строго в соответствии с правилами абстрактной машины (то есть все операции записи завершаются в некоторый момент до следующей точки следования). Это означает, что в рамках одного потока выполнения доступ к volatile не может быть исключен при оптимизации или переупорядочен относительно другого видимого побочного эффекта, который отделен точкой следования от доступа к volatile.
Приведение не-volatile значения к volatile типу не имеет эффекта. Для доступа к не-volatile объекту с использованием volatile семантики, его адрес должен быть приведен к указателю-на-volatile, и затем доступ должен осуществляться через этот указатель.
Любая попытка чтения или записи объекта, тип которого квалифицирован как volatile, через не-volatile lvalue приводит к неопределённому поведению:
volatile int n = 1; // объект типа с квалификатором volatile int* p = (int*)&n; int val = *p; // неопределённое поведение
Член структуры или объединения с квалификатором volatile приобретает квалификацию типа, к которому он принадлежит (как при доступе с помощью оператора
.
, так и оператора
->
):
struct s { int i; const int ci; } s; // тип s.i - int, тип s.ci - const int volatile struct s vs; // типы vs.i и vs.ci - volatile int и const volatile int
|
Если тип массива объявлен с квалификатором volatile (через использование
|
(до C23) |
|
Тип массива и его тип элементов всегда считаются идентично квалифицированными как volatile. |
(начиная с C23) |
typedef int A[2][3]; volatile A a = {{4, 5, 6}, {7, 8, 9}}; // массив массивов volatile int int* pi = a[0]; // Ошибка: a[0] имеет тип volatile int* void *unqual_ptr = a; // OK до C23; ошибка начиная с C23 // Примечание: clang применяет правило из C++/C23 даже в режимах C89-C17
Если тип функции объявлен с квалификатором volatile (с использованием
typedef
), поведение не определено.
|
В объявлении функции ключевое слово
Следующие два объявления объявляют одну и ту же функцию: void f(double x[volatile], const double y[volatile]); void f(double * volatile x, const double * volatile y); |
(since C99) |
Указатель на не-volatile тип может быть неявно преобразован в указатель на volatile-квалифицированную версию того же или совместимого типа. Обратное преобразование требует явного приведения типа.
int* p = 0; volatile int* vp = p; // OK: добавление квалификаторов (int к volatile int) p = vp; // Ошибка: отбрасывание квалификаторов (volatile int к int) p = (int*)vp; // OK: приведение типа
Обратите внимание, что указатель на указатель на
T
не конвертируется в указатель на указатель на
volatile T
; для совместимости двух типов их квалификаторы должны быть идентичны:
char *p = 0; volatile char **vpp = &p; // Ошибка: char* и volatile char* несовместимые типы char * volatile *pvp = &p; // OK, добавляет квалификаторы (char* к char*volatile)
Содержание |
Применение volatile
static
volatile
объекты моделируют порты ввода-вывода с отображением в память, а
static
const
volatile
объекты моделируют входные порты с отображением в память, такие как часы реального времени:
volatile short *ttyport = (volatile short*)TTYPORT_ADDR; for(int i = 0; i < N; ++i) *ttyport = a[i]; // *ttyport является lvalue типа volatile short
static
volatile
объекты типа
sig_atomic_t
используются для взаимодействия с
signal
обработчиками.
volatile
переменные, которые являются локальными для функции, содержащей вызов макроса
setjmp
— это единственные локальные переменные, которые гарантированно сохраняют свои значения после возврата из
longjmp
.
Обратите внимание, что volatile-переменные не подходят для взаимодействия между потоками; они не обеспечивают атомарность, синхронизацию или порядок памяти. Чтение volatile-переменной, которая изменяется другим потоком без синхронизации, или конкурентная модификация из двух несинхронизированных потоков является неопределенным поведением из-за состояния гонки данных.
Ключевые слова
Пример
демонстрирует использование volatile для отключения оптимизаций
#include <stdio.h> #include <time.h> int main(void) { clock_t t = clock(); double d = 0.0; for (int n = 0; n < 10000; ++n) for (int m = 0; m < 10000; ++m) d += d * n * m; // reads from and writes to a non-volatile printf("Modified a non-volatile variable 100m times. " "Time used: %.2f seconds\n", (double)(clock() - t)/CLOCKS_PER_SEC); t = clock(); volatile double vd = 0.0; for (int n = 0; n < 10000; ++n) for (int m = 0; m < 10000; ++m) { double prod = vd * n * m; // reads from a volatile vd += prod; // reads from and writes to a volatile } printf("Modified a volatile variable 100m times. " "Time used: %.2f seconds\n", (double)(clock() - t)/CLOCKS_PER_SEC); }
Возможный вывод:
Modified a non-volatile variable 100m times. Time used: 0.00 seconds Modified a volatile variable 100m times. Time used: 0.79 seconds
Ссылки
- Стандарт C17 (ISO/IEC 9899:2018):
-
- 6.7.3 Квалификаторы типа (стр. 87-90)
- Стандарт C11 (ISO/IEC 9899:2011):
-
- 6.7.3 Квалификаторы типа (стр: 121-123)
- Стандарт C99 (ISO/IEC 9899:1999):
-
- 6.7.3 Квалификаторы типа (стр: 108-110)
- Стандарт C89/C90 (ISO/IEC 9899:1990):
-
- 6.5.3 Квалификаторы типа
Смотрите также
|
C++ documentation
для
cv (
const
и
volatile
) квалификаторов типа
|