Перейти к содержанию

Последнее обновление:2026-10-12

C++ ключевое слово volatile

Об авторе:

Личный официальный аккаунт:

wechat

1.volatile

Переменная, модифицированная volatile, при операциях чтения и записи может вызывать некоторые наблюдаемые побочные эффекты, причём эти наблюдаемые побочные эффекты определяются факторами вне программы.

2.Применение volatile

(1)Аппаратные регистры параллельных устройств (например, регистры состояния). Пусть нужно инициализировать устройство, и один из его регистров находится по адресу 0xff800000.

int  *output = (unsigned  int *)0xff800000; //定义一个IO端口;  
int   init(void)  
{  
    int i;  
    for(i=0;i< 10;i++)
    {  
    *output = i;  
    }  
}
После оптимизации компилятором компилятор сочтёт, что весь предыдущий цикл — бессмыслица, не влияющая на конечный результат, поскольку в итоге указателю output просто присваивается значение 9; поэтому компилятор в итоге скомпилирует код, эквивалентный следующему:
int  init(void)  
{  
    *output = 9;  
}
Если процесс инициализации этого внешнего устройства требует последовательного присваивания значений, как в коде выше, очевидно, что оптимизация не позволит достичь цели. И наоборот: если вы не выполняете повторные операции записи в этот порт, а повторные операции чтения, результат тот же — после оптимизации компилятор, возможно, выполнит чтение из этого адреса лишь один раз, хотя с точки зрения кода никаких проблем нет. Вот здесь и следует использовать volatile, чтобы сообщить компилятору, что эта переменная нестабильна и при встрече с ней оптимизировать её не нужно.

(2)Переменные, к которым обращается подпрограмма обработки прерывания;

static int i=0;

int main()
{
    while(1)
    {
    if(i) dosomething();
    }
}

/* Interrupt service routine */
void IRS()
{
    i=1;
}
Замысел приведённого примера таков: при возникновении прерывания подпрограмма обработки прерывания IRS реагирует на него, изменяя переменную программы i, чтобы в функции main вызвалась функция dosomething; однако, поскольку компилятор считает, что в main переменная i не изменяется, он может выполнить чтение i в некий регистр лишь один раз, а затем каждое сравнение if будет использовать только эту «копию i» из регистра, из-за чего dosomething никогда не будет вызвана. Если же добавить к переменной i модификатор volatile, компилятор гарантирует, что операции чтения и записи переменной i не будут оптимизированы, благодаря чему изменение переменной i внешней программой будет своевременно замечено в исходной программе.

(3)Переменные, разделяемые несколькими задачами в многопоточном приложении. Когда несколько потоков разделяют некоторую переменную и её значение может быть изменено одним из потоков, её следует объявлять volatile. Роль этого — не дать компилятору оптимизацией поместить переменную из памяти в регистр CPU, когда после изменения переменной одним потоком она не будет своевременно синхронизирована с другими потоками, что приведёт к ошибке программы. volatile означает, что компилятор при каждой операции с этой переменной обязан действительно считывать её из памяти, а не использовать уже находящееся в регистре значение. Пример:

volatile  bool bStop=false;  //bStop 为共享全局变量  
//第一个线程
void threadFunc1()
{
    ...
    while(!bStop){...}
}
//第二个线程终止上面的线程循环
void threadFunc2()
{
    ...
    bStop = true;
}
Чтобы второй поток мог остановить цикл первого потока, если bStop не определён с volatile, цикл станет бесконечным: bStop уже считан в регистр, и значение bStop в регистре никогда не станет FALSE; с volatile программа при каждом выполнении считывает значение bStop из памяти, и бесконечный цикл не возникнет.

Знание сценариев применения volatile — эффективный способ отличить программиста C/C++ от программиста встраиваемых систем: разработчики встраиваемых систем постоянно имеют дело с аппаратурой, прерываниями, RTOS и т. д., где всё это требует volatile-переменных; непонимание volatile принесёт катастрофу при проектировании программ.

3.Частые вопросы о volatile

По приведённым ниже вопросам можно понять, действительно ли кандидат разбирается в volatile. (1)Может ли параметр быть одновременно const и volatile? Почему? Да. Пример — регистр состояния, доступный только для чтения. Он volatile, потому что может неожиданно изменяться. Он const, потому что программа не должна пытаться его изменять.

(2)Может ли указатель быть volatile? Почему? Да, хотя это нечасто встречается. Пример — когда подпрограмма обработки прерывания изменяет указатель на буфер.

(3)Что не так с приведённой ниже функцией?

int square(volatile int *ptr) 
{ 
return *ptr * *ptr; 
} 
Этот код немного экстравагантен: его цель — вернуть квадрат значения, на которое указывает указатель ptr; но поскольку ptr указывает на volatile-параметр, компилятор сгенерирует код, подобный следующему:
int square(volatile int *ptr) 
{ 
int a,b; 
a = *ptr; 
b = *ptr; 
return a * b; 
} 
Поскольку значение *ptr может неожиданно измениться, a и b могут оказаться разными. В результате этот код может вернуть вовсе не то значение квадрата, которое вы ожидаете! Правильный код таков:
long square(volatile int *ptr) 
{ 
int a=*ptr; 
return a * a; 
} 

4.Использование volatile

  • Ключевое слово volatile — это модификатор типа; переменные объявленного с его помощью типа могут изменяться факторами, неизвестными компилятору (операционная система, аппаратура, другие потоки и т. д.). Поэтому volatile сообщает компилятору, что такие объекты не следует оптимизировать.

  • Переменная, объявленная с ключевым словом volatile, при каждом обращении должна считывать значение из памяти (переменные без модификатора volatile из-за оптимизаций компилятора могут браться из регистров CPU)

  • const может быть volatile (например, регистр состояния, доступный только для чтения)
  • Указатель может быть volatile

Код для изучения:

Похожие темы

Комментарии