volatile 易变的
volatile
是 C 和 C++ 中的一个类型修饰符,用于指示编译器该变量可能在程序之外被更改,因此不应对其进行优化。这在涉及硬件寄存器、信号处理或多线程编程时非常有用。
如果你做过单片机开发,你肯定写过这样的代码:延时函数。就是通过循环延长CPU的时间,常见于流水灯、蜂鸣器这些对时间精度要求不高的地方,但是这样的代码却存在着很严重的缺陷,如果编译的过程中开启了优化,有些编译器会认为这几行代码是废话,什么事情也没干,在寄存器中会跳过这部分代码。
于是,在C语言中有了一个很重要的关键字volatile
,它的作用是防止编译的时候被优化,在变量之前加上volatile
,编译的时候即便开启最高等级的优化gcc test.c -o test -03
,原代码中的编译将不再遭到优化处理。volatile
在嵌入式开发中非常的重要而且常用,尤其是操作硬件或者多线程的场景。因为编译器在很多时候为了提升程序的运行效率,会跳过和优化掉一些没用的代码,或者直接从寄存器中或者缓存中读取某些变量的值,因为从寄存器或者缓存中取值速度要比内存快,如果这个变量是个共享数据,或者有可能被中断等程序修改,那么,我们读取到的数据就有可能跟内存中的数据不一致。如果在编译中发生这样的事,接下来程序执行的过程中就会导致一系列的问题。
int a = 10; // 在内存占用4个字节空间,该空间取名为a,cpu将10存入内存a空间中int b = a; // 在内存占用4个字节空间,该空间取名为b,cpu将内存a的值取出来,保存在cpu寄存器中(寄存器a)// 将寄存器a 赋值给 内存b空间
int c = a; // 在内存占用4个字节空间,该空间取名为c,将寄存器a 赋值给 内存c空间
int d = a; // 在内存占用4个字节空间,该空间取名为d,将寄存器a 赋值给 内存d空间
int e = a; // 在内存占用4个字节空间,该空间取名为e,将寄存器a 赋值给 内存e空间// 假如 在int c = a的时候,不惊动cpu 去修改了内存中a的值,那么我们期望 d 和 e 的值 要和修改后a 的值 一致
// 但是 d 和 e 中依旧存入的是在 寄存器a 的值,造成 d 和 e 的值达不到我们的期望 // 更改方式:(初始化 int a 更改为 volatile)
volatile int a = 10; //防止CPU对a变量进行优化,从此以后只要用到a的值,就必须去内存中重新读取 —— 每次必须区内存中拿,不去CPU中拿
加上volatile
就是告诉编译器这个变量是不稳定的
,无论编译器怎么优化,每次都得从内存中去读取,
volatile
的用途:
- 硬件寄存器访问:
- 直接访问:确保每次访问硬件寄存器时都使用 volatile,以避免编译器优化。因为访问硬件设备的寄存器时,寄存器的值可能会在程序之外改变。
volatile uint32_t *timer_register = (uint32_t *)0x40000000;*timer_register = 0x01;
- 信号处理/中断处理:
- 标志变量:将中断服务程序(ISR)中使用的标志变量声明为 volatile。
- 在信号处理程序中使用的变量,因为信号可以在任何时候中断程序执行。
volatile int interrupt_flag = 0;void ISR_Handler() {interrupt_flag = 1;}
- 多线程和共享变量:
- 共享状态:在多线程应用中,声明那些在不同线程间共享且不受锁保护的变量为 volatile。
- 在多线程环境中,一个线程可能会修改另一个线程正在读取的变量。
volatile bool data_ready = false;
示例:
#include <stdint.h>#include <stdbool.h>volatile bool data_ready = false;volatile uint32_t *adc_result = (uint32_t *)0x40001000;void ADC_ISR() {*adc_result = // 读取ADC值data_ready = true;}int main() {while (1) {if (data_ready) {// 处理ADC结果data_ready = false;}}return 0;}
通过正确使用 volatile,可以确保嵌入式系统中硬件和软件的正确交互。
简单的说,volatile
是禁止编译器对该变量的访问进行优化(如缓存、寄存器存储),每次访问该变量时,都会从内存中读取或写入。
使用的时候需要注意的事项:
- 仅用于必要情况:不要滥用
volatile
,只在有硬件交互或异步变化时使用。 - 结合同步机制:
volatile
不能替代互斥锁、信号量等同步机制,需要与其他同步机制(如互斥锁、原子操作)结合使用以确保多线程安全。 - 读写原子性:
volatile
不能保证读写操作的原子性,需要时使用原子操作或禁用中断。 volatile
不能保证线程安全,它仅防止编译器优化。
以上。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!