volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile(中断函数修改全局变量,非中断函数读)
2、多任务环境下各任务间共享的标志应该加volatile;
3、指针指到硬件寄存器,通常也要加volatile说明,因为每次对它的读写都可能有不同意义;
说到底,还是全局资源的读写,保证可见性,操作不可优化,仅保证防止编译器优化。但是volatile类型变量无法保证原子操作。
IAR出现警告:undefined behavior: the order of volatile accesses is undefined in this statement
volatile unsigned char a,b;
.......................
unsigned char c;
c = a + b;
出现警告:undefined behavior: the order of volatile accesses is undefined in this statement
由于a和b这两个变量都是volatile,volatile字面意思是“易失,易挥发”的意思,加了这个修饰的变量,编译器在每次使用它的时候都要从该变量的内存地址取出变量值,而不能使用暂存在寄存器中的值。如果a和b都是volatile型的,编译器在做a+b运算的时候就不能绝对地不使用寄存器中的值,取变量值肯定会有先有后,然后再相加。而这个取变量的先后顺序是未定义的,所以出现了那个警告。
解决方法1:
可以把其中一个volatile变量先赋值给非volatile,具体如下:
volatile unsigned char a,b;
.......................
unsigned char c,d;
d = a;
c = a + b;
解决方法2:
![](https://images2015.cnblogs.com/blog/867145/201703/867145-20170307103245406-121926746.png)
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里的备份。
Volatile和register的对比
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的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
volatile深入分析之“volatile与指针”
volatile修饰指针一般用在共享指针上面。
下面代码:
行代码里volatile修饰的是reg这个变量。所以这里实际上是定义了一个uchar类型的指针,并且这个指针变量本身是volatile 的。但是指针所指的内容并不是volatile的!在实际使用的时候,编译器对代码中指针变量reg本身的操作不会进行优化,但是对reg所指的内容 reg却会作为non-volatile内容处理,对reg的操作还是会被优化。通常这种写法一般用在对共享指针的声明上,即这个指针变量有可能会被中断等函数修改。将其定义为volatile以后,编译器每次取指针变量的值的时候都会从内存中载入,这样即使这个变量已经被别的程序修改了当前函数用的时候也能得到修改后的值(否则通常只在函数开始取一次放在寄存器里,以后就一直使用寄存器内的副本)。
需要注意将上述代码与下面的代码进行区别
这行代码里volatile修饰的是指针所指的内容。所以这里定义了一个uchar类型的指针,并且这个指针指向的是一个volatile的对象。但是指针变量本身并不是volatile的。如果对指针变量reg本身进行计算或者赋值等操作,是可能会被编译器优化的。但是对reg所指向的内容 reg的引用却禁止编译器优化。因为这个指针所指的是一个volatile的对象,所以编译器必须保证对reg的操作都不被优化。通常在驱动程序的开发中,对硬件寄存器指针的定义,都应该采用这种形式。
而
volatile uchar * volatile reg;
这样定义出来的指针就本身是个volatile的变量,又指向了volatile的数据内容。