C语言volatile类型限定符详解

一、volatile类型限定符

volatile是一个类型修饰符(type specifier),就像我们熟悉的const一样,它是被设计用来修饰被不同线程访问和修改的变量;volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。

volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。简单地说就是防止编译器对代码进行优化。

各种不同的解释:
1、volatile限定符告知计算机,代理(而不是变量所在的程序)可以改变该变量的值。通常,它被用于硬件地址以及在其他程序或同时运行的线程中共享数据。例如:一个地址上可能存储着当前的时钟时间,无论程序做什么,地址上的值都随时间的变化而改变。或者一个地址用于接收另一台计算机传入的信息。

2、volatile属于C语言的关键字,《C Primer Puls》 是这样解释关键字的:关键字是C语言的词汇,由于编译器不具备真正的智能,所以你必须用编译器能理解的术语表示你的意图。开发者告诉编译器该变量是易变的,无非就是希望编译器去注意该变量的状态,时刻注意该变量是易变的,每次读取该变量的值都重新从内存中读取。

3、volatile 影响编译器编译的结果,volatile指出 变量是随时可能发生变化的与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会 进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化。)

4、volatile是C语言中的一个关键字。将变量定义为volatile就表示告诉编译器这个变量可能会被竟想不到地改变,在这种情况下,编译器就不会去假设这个变量的值了,及优化器在用到这个变量是必须每次重新读取他的值。

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

二、编译器优化

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

void Barrier(void)

这个函数通知编译器插入一个内存屏障,但对硬件无效,编译后的代码会把当前CPU寄存器中的所有修改过的数值存入内存,需要这些数据的时候再重新从内存中读出。

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

三、volatile应用示例

C语言编译器一般都有优化的功能,对代码进行优化。例如:

int tmp, a1, a2;
tmp = (unsigned int *)0x4004;
a1 = *tmp;
a2 = *tmp;

在某些编译器中,这段代码很可能被编译器优化,优化的结果等同如下代码。

int tmp, a1, a2;
tmp = (unsigned int *)0x4004;
a1 = *tmp;
a2 = a1;

这种优化在一般的情况下没有什么错误,但是在特殊的情况下却可能引发错误。例如:第一次读操作(a1 = *tmp)后,*tmp的内容有可能已经被更新,在这种情况下,第2次读操作读出的内容与第一次不一样。原本程序的含义也是在两个不同的时刻读出两个不同的值,但是经过优化后的程序只能读出相同的值。这就需要使用volatile关键字。上述的程序修改后如下形式:

volatile unsigned int *tmp;
int a1, a2;
tmp = (volatile unsigned int *)0x4004;
a1 = *tmp;
a2 = *tmp;

volatile在嵌入式系统中普通用于可能具有并行操作性质的数据,这些变量可能是被外部改变或者内部并行的程序改变

四、volatile应用场景

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

volatile static int i=0;
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修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。

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

3、存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义。

#define PINSEL0 (*((volatile unsigned long *) 0xE002C000))
#define PINSEL1 (*((volatile unsigned long *) 0xE002C004))
#define PINSEL2 (*((volatile unsigned long *) 0xE002C008))
#define PINSEL3 (*((volatile unsigned long *) 0xE002C00C))

五、volatile官方说明

volatile官方描述

表明变量能被后台程序修改
关键字volatile和const是完全相反的。它表明变量可能会通过某种方式发生改变,而这种方式是你通过分析正常的程序流程完全预测不出来的。(例如,一个变量可能被中断处理程序修改)。关键字使用语法如下:

volatile data-definition;

每次对变量内容的引用会重新从内存中加载而不是从变量在寄存器里面的拷贝加载。

个人理解:变量就是一块编了地址的内存区域,GPIO的数据寄存器有一个地址,大小一般为32bit,所以这个数据寄存器可以认为就是一个变量,我们可以读写它。如果GPIO设置为输入,修改GPIO数据寄存器这个变量的就是这个GPIO的引脚,此刻读到数据和下一刻读到的完全可能是不一样的。简单的说就是前后数据不同步。使用volatile修饰后,会强制你每次引用GPIO寄存器对应的变量时都会去它的寄存器里面读。

volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错

六、面试问题

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

答:是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

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

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

3)下面的函数被用来计算某个整数的平方,它能实现预期设计目标吗?如果不能,试回答存在什么问题:

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

这段代码是个恶作剧。这段代码的目的是用来返指针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;
}

参考资料:
http://tigcc.ticalc.org/doc/keywords.html#volatile
https://blog.csdn.net/tigerjibo/article/details/7427366

  • 2
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值