C语言volatile 关键字

什么是volatile关键字

volatile用于声明一个变量,告诉编译器该变量值容易发生改变,在编译、读取、存储该变量的时候都不要做任何优化,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取存储数据,不做优化,在做嵌入式开发的时候,因为有时变量地址有可能是系统的一个外设地址,他的值的变化并不在程序控制范围内,随时有可能变化,因此需要对他进行声明,每次读取或者存储直接对地址进行操作

变量如果加了 volatile 修饰,则会从内存重新装载内容,而不是直接从寄存器拷贝内容

为什么使用volatile关键字

const 和 volatile 关键字是一种类型修饰符,两个是绝对对立的。volatile 的作用 是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。

什么时候使用volatile关键字

1、并行设备的寄存器(例如状态寄存器),存储器映射的硬件寄存器通常加volatile。

        设备寄存器会在你的程序不知道或者不介入的时候发生改变,那是因为设备寄存器可以被外设硬件修改,相反,变量中的不会变。设备寄存器的内容是易失的,或者在不注意的时候被修改,当声明指向设备寄存器的指针时一定要用volatile,它会告诉编译器不要对存储在这个地址的数据进行假设,编译器在优化这个变量时应该把它看作编译时未知的。

2、一个中断服务程序中修改的供其它程序检测的变量。

由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化,例如:

static int i=0; //i 为非自动变量
int main(void)
{
     ...
     while (1){
     if (i) dosomething();
     }
}
/* Interrupt service routine. */
void ISR_2(void)
{
      i=1;
}

程序的本意是希望 ISR_2 中断产生时,在main函数中调用 dosomething 函数,但是,由于编译器判断在 main 函数里面没有修改过 i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致 dosomething 永远也不会被调用。如果将变量加上 volatile 修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。

3、多线程应用中被几个任务共享的变量(防止编译器对代码进行优化)。

        当两个线程都要用到某一个变量且该变量的值会被改变时,应该用 volatile 声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值

上面说到volatile关键字主要是防止被编译器优化或改变,那么上面是编译器优化呢?

GCC优化

通常C语言的编译器使用的是GCC编译器,他有个以下几个等级的优化

  • -O0 :(默认):没有优化。
  • -O或-O1 :优化,但不要花费太多的时间。
  • -O2 :更积极地优化
  • -O3 :最积极地优化
  • -Ofast :最高级的优化
  • -Os : 优化代码大小
  • -Og :在尽量不干扰调试的情况下优化

开启不同的优化等级,程序编译后的结果就会发生一定的变化,而volatile就是在开启优化的情况下使用,保护变量不被优化。

测试:

//示例一
#include<stdio.h>

int main()
{
        int i = 0;
        i = 1;
        i = 2;
        i = 3;
        printf("%d\n",i);
        return 0;
}
//示例二
#include<stdio.h>

int main()
{
        volatile int i = 0;
        i = 1;
        i = 2;
        i = 3;
        printf("%d\n",i);
        return 0;
}

 使用-O默认优化等级对代码进行编译,查看汇编结果。

 

 

通过查看汇编结果,我们可以看到不使用volatile 优化后,i=0,i=1,i=2被优化,没有生成汇编的代码,只生成了i=3的汇编代码,取最后的值寻址赋值给 i 输出。而在使用volatile声明变量 优化后,每一个i 的赋值都生成了汇编代码(上图红框),没有被优化,每次赋值都重新寻址。

可以清楚的看到:使用 volatile 的代码编译未优化。如果 i 是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。

编译器优化介绍
由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。由编译器优化或者硬件重新排序引起的问题的解决办法是在从硬件(或者其他处理器)的角度看必须以特定顺序执行的操作之间设置内存屏障(memory barrier)Linux 提供了一个宏解决编译器的执行顺序问题。
void Barrier(void)
这个函数通知编译器插入一个内存屏障,但对硬件无效,编译后的代码会把当前CPU寄存器中的所有修改过的数值存入内存,需要这些数据的时候再重新从内存中读出。

2、volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以消除一些代码。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化。

volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)。


volatile 常见问题

1、一个参数可以是const还可以是volatile吗?

可以。比如只读的状态寄存器。它是volatile因为它可能被意想不到的改变,它是const因为 程序不应该试图去修改它。

2、一个指针可以是volatile吗?

可以。当一个服务中子程序修改一个指向buffer的指针时。

3、下面的函数段有问题吗?

int square(volatile int *ptr) 
{ 
    return *ptr * *ptr; 
} 

*ptr指向一个volatile型参数,*ptr的值随时可能被改变,所以代码段返回的可能不是所期望的平方值。

4、C语言编译过程中,volatile关键字和extern关键字分别在哪个阶段起作用
       volatile应该是在编译阶段,extern在链接阶段。
       volatile关键字的作用是防止变量被编译器优化,而优化是处于编译阶段,所以volatile关键字是在编译阶段起作用。
 

程序编译运行阶段:C语言编译运行代码的过程_◣星河◢的博客-CSDN博客

参考文章:C语言再学习 -- 关键字volatile_聚优致成的博客-CSDN博客_volatile关键字c语言

  • 11
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

◣星河◢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值