C语言中关键字volatile

volatile关键字用于指示编译器变量的值可能会被意想不到的情况改变,避免编译器进行优化。文章介绍了volatile的作用,通过实例解析了其在内存取值、多线程共享变量和中断服务程序中的应用,并探讨了与const、指针结合使用时的情况。

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

C语言中关键字volatile

volatile 的英文解释是——“易失的,易改变的。顾名思义,这个关键字的含义是向编译器指明变量的内容可能会由于编译器意想不到的情况的变化而发生变化这个解释仍然比较抽象,感兴趣的可以继续阅读下面内容。

 

先看一下编译器对程序的优化过程是怎么进行的

如果编译器在代码中发现对同一地址的两次访问之间,没有对该地址进行写操作,那么编译器的优化过程认为——第一次寻址读取该地址时取得的变量的值(编译器会尽最大可能把这个值存放在通用寄存器R中或者cache)作为第二次寻址的值,而并不是再做第二次内存的 I/O 寻址操作(CPU把该变量的值放到通用寄存器或者cache中后就不会再关心对应内存中的值)。

例如:

int i = 10;

int j = i;  // (1)语句

int k = i;  // (2)语句

因为在(1)、(2)两条语句中,i没有被用作左值同一地址的两次访问之间,没有对该地址进行写操作),这时候编译器认为i的值没有发生变化,所以在(1)语句时,从内存中取出i的值赋给j之后,这个值并没有丢掉(存放在通用寄存器中),而是在(2)语句时继续用这个值给k赋值。编译器不会生成汇编代码而重新从内存里寻址i的值。

 

Volatile这个关键字的必要性(真正意义之所在)

但其他程序(例如内核程序或一个中断)修改了内存中该变量的值,此时寄存器R中的值并不会随之改变而更新,由于优化器的作用编译器仍然去利用之前存放在寄存器R中的值,而不去寻址内存中的值(但我们必须改变这个变量的值)。为了解决这种情况C语言就引入了volatile限定词,让代码在引用该变量时多费一点劲儿,再去内存中取出该变量的值。

例如:

Volatile int i = 10;

int j = i;  // (3)语句

int k = i; // (4)语句

         这里,volatile关键字告诉编译器i是随时可能发生变化的,每次使用它的时候必须从内存中取出i的值,因而编译器生成的汇编代码会重新从内存中i的地址处读取i的值存放在k中。

一句话概括就是,当用volatile关键字修饰变量时,优化器在用到这个变量时必须每次都小心地去内存重新读取(关键之处)这个变量的值,而不是使用保存在寄存器R里的备份。

 

Volatileregister的对比

volatile 跟以前的 register 相反。 register 告诉编译器尽量将变量放到寄存器中使用, volatile 强制将更改后的值写回内存。如果不写回内存,对于一些全局共享的变量,可能导致不一致问题。

 

volatie变量将和cache不发生关系,只和内存有关系

简单点说就是每次操作前从内存取值

volatie修饰的变量,每次操作时遵循下面动作:

从内存取值 ---> 放入寄存器 ----> 操作 ----> 写回内存

没有volatie修饰的变量,操作可能遵循:

从内存取值 ---> 放入寄存器 ----> 第一次操作 -----> 第二次操作(此时仍操作寄存器中的值) …… ----> N次操作 ----> 写回内存

 

举个例子论述两者关系

int volatie i;  //全局变量,在其它地方会被修改

 

while (i)

{

do_somethings();

}

如果i没有被volatie修饰,当while循环执行时,另一段程序并发的执行了i = 0 这个循环仍不会退出,因为每次循环都是检查寄存器中的值。

如果有volatie修饰,那么循环结束,因为循环每次检查i的时候,会先从内存把i读入寄存器,这个时候i在其它地方被赋0,则循环结束。 

 

Volatile关键字应用的三个地方

1、   修改硬件寄存器,尤其是状态寄存器

    大家都知道,在硬件级别,如果寄存器值自动改变了,编译器是不会主动发现的。经过编译器的自动优化,我们读到的都是寄存器中存储的旧的状态寄存器的值, 而非最新的状态寄存器值例如:

l         #define STATUS  (*(volatile unsigned long *)0x56000010)  

2、  多线程中被几个线程共享的变量

    线程修改共享变量var是不会通知编译器的。所以线程A坚持不懈地读着var在寄存器或者cache中的副本,读出来的内容是0, 但很可惜,线程B早就把var变量给修改为1了。鉴于此,我们必须加上volatile这个关键字来解决这个问题。

3、  中断服务程序ISR当中用

    ISR:中断服务程序 interrupt service routine

  所谓中断是指当CPU正在处理某件事情的时候,外部发生的某一事件(如一个电平的变化,一个脉冲沿的发生或定时器计数溢出等)请求CPU迅速去处理,于是CPU暂时中止当前的工作,转去处理所发生的事件。中断服务处理完该事件以后,再回到原来被中止的地方继续原来的工作。

 

volatile引出的三个问题

1)一个参数既可以是const还可以是volatile吗?解释为什么。

2)一个指针可以是volatile 吗?解释为什么。

3)下面的函数有什么错误:

int square(volatile int *ptr)

{return *ptr * *ptr;}

答:

1)是的。一个典型的个例子就是只读的状态寄存器

¨          它是volatile因为它可能被意想不到地改变。

¨          它是const因为程序不应该试图去修改它。

2)是的。尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。

3)这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,


 

编译器将产生类似下面的代码:

int square(volatile int *ptr)

{

int a, b;

a = *ptr;

b = *ptr;

return a * b;

}

由于*ptr的值可能被意想不到地该变,因此ab可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)

{

int a;

a = *ptr;

return a * a;

}

### 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、付费专栏及课程。

余额充值