请大侠讲讲volatile的作用
请教下面两句有什么区别:unsigned char c1
volatile unsigned char c1
谢谢 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得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;
} 谢谢讲解!
-----此内容被quben于2005-07-07,15:44:40编辑过 点击此处打开armok0155748.pdf
看看附件,有说明. 谢谢
volatile的本意是“易变的”
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
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也应该如此说明。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实
现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。 讲讲我的理解: (欢迎打板子...~~!)
关键在于两个地方:
1. 编译器的优化(请高手帮我看看下面的理解)
在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;
当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致
当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致
当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致
举一个不太准确的例子:
发薪资时,会计每次都把员工叫来登记他们的银行卡号;一次会计为了省事,没有即时登记,用了以前登记的银行卡号;刚好一个员工的银行卡丢了,已挂失该银行卡号;从而造成该员工领不到工资
员工 -- 原始变量地址
银行卡号 -- 原始变量在寄存器的备份
2. 在什么情况下会出现(如1楼所说)
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
补充: volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人;
“易变”是因为外在因素引起的,象多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;
而用volatile定义之后,其实这个变量就不会因外因而变化了,可以放心使用了; 大家看看前面那种解释(易变的)是不是在误导人
------------简明示例如下:------------------
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
使用该关键字的例子如下:
int volatile nVint;
>>>>当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
例如:
volatile int i=10;
int a = i;
...
//其他代码,并未明确告诉编译器,对i进行过操作
int b = i;
>>>>volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
>>>>注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:
>>>>首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码:
>>
#include <stdio.h>
void main()
{
int i=10;
int a = i;
printf("i= %d",a);
//下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
__asm {
mov dword ptr , 20h
}
int b = i;
printf("i= %d",b);
}
然后,在调试版本模式运行程序,输出结果如下:
i = 10
i = 32
然后,在release版本模式运行程序,输出结果如下:
i = 10
i = 10
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。下面,我们把 i的声明加上volatile关键字,看看有什么变化:
#include <stdio.h>
void main()
{
volatile int i=10;
int a = i;
printf("i= %d",a);
__asm {
mov dword ptr , 20h
}
int b = i;
printf("i= %d",b);
}
分别在调试版本和release版本运行程序,输出都是:
i = 10
i = 32
这说明这个关键字发挥了它的作用!
------------------------------------
volatile对应的变量可能在你的程序本身不知道的情况下发生改变
比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量
你自己的程序,是无法判定合适这个变量会发生变化
还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。
对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。
--------------------------------------------------------------------------------
典型的例子
for ( int i=0; i<100000; i++);
这个语句用来测试空循环的速度的
但是编译器肯定要把它优化掉,根本就不执行
如果你写成
for ( volatile int i=0; i<100000; i++);
它就会执行了
-----此内容被quben于2005-07-07,18:37:08编辑过 quben 去笨分析的很详细,很清晰,顶~! 恩!终于明白为什么有的程序在被优化后就工作不正常了!!!
多谢啊! 我也来顶一下! 受益匪浅,最近就在为一个多线程的程序为什么阵发性的跑错找不到原因。
加上volatile以后,故障排除! 好,不顶就说不过去了。 讲的透彻,受益匪浅,多谢多谢! 好贴,顶一下! 非常有用,解决了多天的难题。感谢! 那么是不是所有的变量都可以加上 volatile ? 敬礼!!!讲的透彻,受益匪浅. 抛砖引玉啊! good! 讲的透彻,受益匪浅,多谢多谢! 呵呵,收教了!
谢谢。 感谢大侠讲解! 好
! 好帖... 再顶一下。 頂!對初學者實在太有用了! 很有用,很好!
顶!! 谢谢 makesoft 和去笨,特别是去笨,讲得真好,你可以去作老师了,谢谢您的耐心. 有用,Mark.. 在ICC里不用优化器,是不是可以用不用volatile? 好!分析的不错. 很有用处~~不顶不仗义阿! 是啊!讲得太好了。 我也顶下,先收藏了,呵呵,谢谢了。 解决我的大问题,不顶不行啊!!!!!! 受益很深!谢谢!原来遇到的问题,终于有点启发了。 不错,很有深度,也很有启发!!
谢谢!
同问:是不是所有用户自己定义的变量都可以定义成volatile呢?? 谢谢收藏了 完全不必要把所有用户自己定义的变量都可以定义成volatile,因为那样编译器凡是遇到用户自己定义的变量就不会去寄存器中读取而直接到内存读取了,结果是降低的程序的性能,所以volatile也要慎用,用多了也有问题的。只需要加在变量有可能被其他条件意外更改的情况下就可以了,大部分变量还是不需要加的。 很长时间没人顶了,我来顶顶. 好贴,胜读10天书!
看来中断中修改的全局变量,要考虑+这个关键字 了。 thank you!
受益很深 好贴,不顶不行!! Very well! Y 状态寄存器怎样声明为volatile 变量?
可以这样声明SPI的状态寄存器SPSR吗?
volatile char SPSR; SPSR在ICCAVR的头文件中有定义
#define SPSR (*(volatile unsigned char *)0x2E) 抱歉,我没有说编译器,我用的是IAR4.10A,不知道怎么定义 我也遇到相同情况,加个volatile 结果就对了 多谢多谢!对volatile一直不是很理解,今天算是明白了! 深有体会! 真的很不错!! 高手呀,讲得真是好!!!狂顶。。。 多谢,又多学东西了. 讲的真好! 果然受益非浅,我的程序因没有volatile失败,因此贴而成功啊.谢谢啦 恩.再次体会了volatile的意义. 谢谢,受益匪浅 我是解决了问题才看到这贴,真是好贴,顶~ 以后使用变量要注意了啊 此贴讲解的比较透彻,我在做工程的时候也遇到过,其实,如果大家多看一看编译器的说明,对每一个关键字的定义描述,理解其含义,也会少走弯路的。 收到! 好 强顶 高手,顶呀。。。。。。。。。。。。。。。。。。。。。。。。。 狂顶! 这种好帖不应该沉下去! 好贴,作个记号 谢谢,听到两位的说明,自己清楚多了,多谢! 好帖一定要顶! 一定要顶! 好帖. 我前段时间写多任务系统,在各任务间是一个循环:
while(1)
{
// ...
}
因为各个任务间有些数据是要共享的,看过编译器编译后的汇编代码,始终不明白我定义的全局变量为什么在此不会执行STS指令,后来改用goto语句:
TaskStart:
// ...
goto TaskStart;
再看结果,问题就解决了。今天一看此贴,才知道如此,我以前没怎么碰GCC的,呵呵,顶! 明白了,顶! 再顶!!
好贴,受教了!! 透彻。鼎 还未看完,顶一下. 顶,透彻~ 我说怎么上次调试一个程序时里面的延时部分没执行
原来被优化掉了
当时只要我把优化级别改为00级它就被执行,为0S级就不被 执行
今天算是明白了! 说得好!我也有过类似问题。中断计数时产生死循环! 好 好贴 恩……
寒……
以前用全局变量的地方都只用static,没有出过问题。
为了健壮以后要加volatile了! 强顶!在这论坛感觉好温暖! 好有收获。
顶一下 好,顶,解决了我的大问题,搞了我一整天,头都大了 多谢多谢!对volatile一直不是很理解,今天算是明白了! 又有进步了
爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽
爽爽爽爽爽爽 爽爽爽爽爽爽爽爽爽爽 爽爽爽 爽爽爽爽爽
爽爽爽爽爽爽 爽爽爽爽爽爽爽 爽爽爽爽 爽爽爽爽爽
爽爽爽爽爽 爽爽爽爽爽爽爽爽爽 爽爽爽爽 爽爽爽爽
爽爽爽爽 爽爽爽爽爽爽爽爽爽爽爽爽 爽 爽爽 爽爽
爽爽 爽 爽爽爽爽爽爽爽爽爽爽 爽爽爽 爽爽 爽
爽爽爽爽爽 爽爽爽爽爽爽爽爽爽 爽爽爽 爽爽爽爽
爽爽爽爽 爽爽爽爽爽爽爽爽爽爽 爽 爽 爽爽爽爽爽
爽爽爽爽爽爽爽 爽爽爽爽爽爽爽爽爽 爽 爽 爽爽
爽爽 爽 爽爽爽爽爽爽爽爽爽 爽爽爽爽 爽爽爽爽
爽 爽爽 爽爽爽爽爽爽爽爽 爽 爽 爽爽爽爽
爽爽爽爽爽 爽爽爽爽爽爽爽爽 爽 爽爽 爽 爽爽爽爽
爽爽爽爽 爽爽爽爽爽爽 爽爽 爽爽爽爽爽 爽爽爽爽
爽爽爽 爽爽 爽爽爽爽 爽 爽爽 爽爽 爽爽爽爽
爽 爽爽爽爽 爽爽爽爽爽 爽爽爽 爽爽爽爽
爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽爽 谢谢,指教 我有个问题,如果我有一些数据都是属于volatile的,而且我将他们包含在一个结构体内。例如:
typedef struct _UART_BUF_
{
unsigned char counter;
unsigned char temp_buffer;
unsigned char uart_high;
unsigned char uart_low;
unsigned intuart_data;
}uartbuf;
uartbuf uartdatum;
引用时应该是
uartdatum.counter
uartdatum.temp_buffer
之类的形式。
为了写为volatile的形式,定义类型的时候是写成下面这种形式呢?
typedef struct _UART_BUF_
{
volatile unsigned char counter;
volatile unsigned char temp_buffer;
volatile unsigned char uart_high;
volatile unsigned char uart_low;
volatile unsigned intuart_data;
}uartbuf;
uartbuf uartdatum;
还是下面这种形式?
typedef struct _UART_BUF_
{
unsigned char counter;
unsigned char temp_buffer;
unsigned char uart_high;
unsigned char uart_low;
unsigned intuart_data;
}uartbuf;
volatile uartbuf uartdatum;
甚至其他形式?
恳请各位大侠不吝赐教。 深有体会的 我个人觉得结构体中的成员应该不用加volatile.不会被优化掉的。 好贴,ding 解释的很详细,正不知道其中的区别。 受益匪浅 高手中的高手,这帖不顶不行啊. 再顶!!! 如此好贴,应该顶! 学习了 难得 收藏 顶,好贴 顶!!!!!!!!!!!!!!!!!!!! 好贴!