C-关键字之volatile

本文详细介绍了C/C++中volatile关键字的作用及其应用场景,包括如何避免编译器优化导致的数据不一致问题,特别是在多任务和中断环境中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在上个文章里提到了volatile关键字,这里进行一下总结。里面有网上查到的,也有我使用STM32单片机时关于volatile的心得。

 

Volatile总是和优化有关,编译器有一种技术叫数据流分析,分析程序中变量在哪里赋值,在哪里使用。但这时有些优化并不为程序需要,这时可以用volatile禁止做这些优化。Volatile的字面含义是易变的。

它有以下作用:

1、不会在两个操作之间把volatile变量缓存到寄存器中。在多任务、中断、jmp下,变量可能被其他程序改变,编译器无法自己知道,volatile就是告诉编译器这种情况。

2、不做常量合并等优化。比如:

            volatile int i =1; 
            if (i > 0) ... 
if的条件不会当作无条件真。 如果i前面没有volatile的话,那if的条件就会被不加判断就认为是真值;现在有了volatile,软件会从内存中读出i的值,然后再来比较判断。

3、volatile变量的读写不会被优化掉。如果你对一个变量赋值但后面没有用到,那编译器常常省略这个操作。

应用举例1:


我用的VS2010,在debug模式下。两段代码输出都是一样的,都是打印10,32。因为debug模式没有对代码进行优化。所以volatile关键字的作用看不出来。

在release模式下,编译器对代码进行了优化(左边的代码中_asm里面的就被优化掉了)。

这样左边的代码输出是10,10;而右边的代码输出是10,32。

注:bsp是栈顶指针,指向下一个压栈时存放数据的地址。这样bsp-4就是i在栈里的地址。

 

应用举例2:往某一个地址发送两次数据

这样就会造成第一条指令丢失,如果用了volatile,就不会被允许优化。

 

Volatile的应用场景:

1、中断服务程序中,修改的其他程序检测的变量,需要加volatile

2、多任务环境下,各任务间共享的标志应加volatile

3、存储器映射的硬件寄存器通常也要用volatile说明,因为每次对它的读写都可能有不同的含义。(这就是STM32的SRAM与外设之间的映射)。

 

         Volatile与STM32:

         大多数 C编译器不支持位段操作。如在使用不同的地址时,C编译器不会认为访问的是同一存储器区域,而且他们也不知道对位段别名的访问只会操作该存储位置的最低位。

         比如:0x40000000与0x4200 0000访问的都是0x4000 0000这一区域,而C编译器会认为它们是两个地址。

在使用位段特性时,变量需被声明为volatileC编译器不知道同一个数据可以在不同的地址中访问。因此volatile属性可以确保每次在操作一个变量时,访问的是存储器位置而不是处理器内部的本地数据复制。


### C语言中 `volatile` 关键字的作用使用场景 #### 1. **`volatile` 的基本定义** 在C语言中,`volatile` 是一种类型限定符,用于告诉编译器该变量可能会被程序外部的因素改变,因此每次访问这个变量时都必须直接从内存读取,而不是依赖于寄存器中的缓存副本[^2]。 #### 2. **`volatile` 的主要作用** - 防止编译器优化:当声明一个变量为 `volatile` 后,编译器不会对该变量进行任何假设性的优化操作。这意味着即使看起来可以省略某些读写操作,编译器也会严格按照源码执行这些操作。 - 确保数据一致性:由于硬件或其他线程可能随时更改此变量的值,通过标记为 `volatile` 可以保证每次使用的都是最新的实际存储值。 #### 3. **典型使用场景** ##### 场景一:中断处理 在一个嵌入式系统中,如果某个全局变量可以在中断服务例程(ISR) 主线程之间共享,则应该将其设置成 `volatile` 类型。这是因为ISR会异步地更新这个变量,而主线程也需要及时感知到这种变化。 ```c volatile uint8_t interrupt_flag = 0; void ISR() { interrupt_flag = 1; } int main() { while (interrupt_flag == 0); // Wait until interrupted return 0; } ``` ##### 场景二:多线程环境下的通信标志位 当多个线程共同维护同一个状态指示量或者信号灯的时候,为了防止因指令重排而导致逻辑错误,应当采用 `volatile` 来修饰此类变量。 ```c #include <pthread.h> volatile int done = 0; void* thread_func(void *arg){ sleep(5); done = 1; pthread_exit(NULL); } int main(){ pthread_t tid; pthread_create(&tid, NULL, thread_func, NULL); while(!done){} /* Busy waiting */ printf("Thread finished.\n"); pthread_join(tid, NULL); return 0; } ``` ##### 场景三:硬件寄存器映射 对于那些直接对应物理设备地址的空间位置来说(比如I/O端口),它们的状态往往是由外界条件决定而非软件控制流程所影响,所以也需用上 `volatile` 定义相应的结构体成员或指针指向这样的区域[^1]。 ```c struct HardwareRegister{ volatile unsigned char control_register; }; #define HW_BASE_ADDR ((struct HardwareRegister *)0xFFFF0000) void set_control_bit(){ HW_BASE_ADDR->control_register |= (1 << 3); } ``` #### 注意事项 虽然 `volatile` 能够解决很多同步问题,但它并不能替代互斥锁来实现复杂的并发保护机制;另外,在现代CPU架构下还可能存在其他层面的乱序现象,仅靠 `volatile` 并不足以完全规避这些问题。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值