volatile关键字

来源:互联网 发布:nginx 域名目录 编辑:程序博客网 时间:2024/05/16 17:20

FROM:百度

volatile关键字修饰说明当这变量被意想不到地改变时,直接存取原始内存地址,确保本条指令不会因编译器的优化而省略,要求每次直接读值。


简单地说就是防止编译器对代码进行优化,比如如下程序:
XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;
对外部硬件而言,上述四条语句分别表示不同的操作,会产生四种不同的动作,但是编译器却会对上述四条语句进行优化,认为只有XBYTE[2]=0x58(即忽略前三条语句,只产生一条机器代码)。如果键入volatile,则编译器会逐一地进行编译并产生相应的机器代码(产生四条代码)。
优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1)并行设备的硬件寄存器(如:状态寄存器)
2)一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3)多线程应用中被几个任务共享的变量


问题:

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;
}
讲讲个人理解:
关键在于两个地方:
⒈编译器的优化(请高手帮我看看下面的理解)
在本次线程内,当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后再取变量值时,就直接从寄存器中取值;
当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致
当变量在因别的线程等而改变了值时,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致
⒉ 在什么情况下会出现
1)并行设备的硬件寄存器
2)一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3)多线程应用中被几个任务共享的变量
补充:volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人;
“易变”是因为外在因素引起的,像多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;
当变量被未知的因素更改(比如:中断、硬件或者其它线程等)时,如果这个变量已经用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的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响: 
#include<stdio.h>
void main(int argc,char *argv[])
{
    int i = 10;
    int a = i;
    printf("i=%d",a);
    //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
    __asm
    {
        mov dword ptr[ebp-4],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(int argc,char *argv[])
{
    volatile int i = 10;
    int a = i;
    printf("i=%d",a);
    __asm
    {
    `    mov dword ptr[ebp-4],20h
    }
    int b = i;
    printf("i=%d",b);
}
分别在调试版本和release版本运行程序,输出都是:
i = 10
i = 32
这说明这个关键字发挥了它的作用!


典型的例子
1 for(int i=0; i<100000; i++);
这个语句用来测试空循环的速度的,但是编译器肯定要把它优化掉,根本就不执行。
如果你写成
1 for(volatile int i=0; i<100000; i++);
它就会执行了。
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
static int i = 0;
int main(void)
{
    //...
    while(1)
    {
        if(i) dosomething();
    }
}
/*Interruptserviceroutine.*/
void ISR_2(void)
{
    i=1;
}
程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此,可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。如果将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。

原创粉丝点击