c++关键字之:volatile
2017-03-30 19:19
344 查看
volatile 是“易变的”、“不稳定”的意思。volatile是 c++ 的一个关键字,用来解决在“共享”环境下容易出现的读取错误的问题。
在单任务的环境中,一个函数体内部,如果在两次读取变量的值之间的语句没有对变量的值进行修改,那么编译器就会设法对可执行代码进行优化。由于访问寄存器的速度要快过RAM(从RAM中读取变量的值到寄存器),以后只要变量的值没有改变,就一直从寄存器中读取变量的值,而不对RAM进行访问。
这虽然在单任务环境下是一个优化过程,但是却是多任务环境下问题的起因。
多任务环境中,虽然在一个函数体内部,在两次读取变量之间没有对变量的值进行修改,但是该变量仍然有可能被其他的程序(如中断程序、另外的线程等)所修改。如果还是从寄存器而不是从RAM中读取变量的值,就会出现被修改了的比阿郎的之不能及时的反应的问题。如下程序对这一现象进行了模拟:
程序在VS2012环境下生成 release 版本(一定要极端优化,vs编译环境下选择优化 速度最大化 /O2),输出结果也是:
10
10
顺便说一下,ebp是扩展基址指针寄存器(extended base pointer) 其内存放一个指针,该指针指向系统栈最上面一个栈帧的底部。本来事实上已经通过内联汇编,修改过的值,为什么打印出来还是10呢。但是如果,将
int i=10; 前加 volatile 就不会发生这种情况了。跟踪汇编代码可以发现,凡是声明为 volatile
的变量,每次拿到的值都是从内存中直接读取的。
以下实验在 vs2012 release 环境下进行。
不加 volatile
加了 volatile
由于编译器的极端优化,可以很明显的看到,在没有加 volatile 的情况下,甚至编译器是直接使用操作数 0Ah 进行运算的。
而在加了 volatile 的情况下,每次都是从 ptr [i] 中读取。而且在速度极端优化的情况下,
也自动 inline 处理了。
但是这里也抛出一个问题,为什么是 [ebp-4] 修改的就是i的值,更奇怪的是,我如果如下这样写代码,那改的会是哪个变量的值呢:
为什么分配的总是 [ebp-4] 是复制给 a 的值呢?试验过,如果将 ic 赋值给 a,那 [ebp-4] 存放的值将会是 ic
阅读以上程序,注意以下几个要点:
(1)以上代码必须在release模式下考查,因为只有Release模式(严格说需要在速度最大优化 /O2)下才会对程序代码进行优化,而这种优化在变量共享的环境下容易引发问题。
(2)凡是需要被多个任务共享的变量(如可能被中断服务程序访问的变量、被其他线程访问的变量等),都应声明为 volatile 变量。而且为了提高执行效率,要减少对 volatile 不必要的使用。
(3)由于优化可能会将一些“无用”的代码彻底去除,所以,如果确实希望在可执行文件中保留这部分代码,也可以将其中的变量声明为 volatile:
在生成 release 版本的程序时,由于循环体每次给 s 的值不变(简化为执行1次),或者说没有使用(1次都没有),但如果此时程序猿是希望循环拖延时间,写成 volatile 就可以了。
附录:问题
1)一个参数既可以是const还可以是volatile吗?解释为什么
是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2) 一个指针可以是volatile 吗?解释为什么
是的。尽管这并不很常见。一个例子是当一个中断服务子程序修该一个指向一个buffer的指针时。
3) 下面的函数有什么错误:
这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是 你所期望的平方值!正确的代码如下:
在单任务的环境中,一个函数体内部,如果在两次读取变量的值之间的语句没有对变量的值进行修改,那么编译器就会设法对可执行代码进行优化。由于访问寄存器的速度要快过RAM(从RAM中读取变量的值到寄存器),以后只要变量的值没有改变,就一直从寄存器中读取变量的值,而不对RAM进行访问。
这虽然在单任务环境下是一个优化过程,但是却是多任务环境下问题的起因。
多任务环境中,虽然在一个函数体内部,在两次读取变量之间没有对变量的值进行修改,但是该变量仍然有可能被其他的程序(如中断程序、另外的线程等)所修改。如果还是从寄存器而不是从RAM中读取变量的值,就会出现被修改了的比阿郎的之不能及时的反应的问题。如下程序对这一现象进行了模拟:
#include <iostream> using namespace std; int main(int argc,char* argv[]) { int i=10; int a=i; cout<<a<<endl; _asm{ mov dword ptr [ebp-4],80 } int b=i; cout<<b<<endl; return 0; }
程序在VS2012环境下生成 release 版本(一定要极端优化,vs编译环境下选择优化 速度最大化 /O2),输出结果也是:
10
10
顺便说一下,ebp是扩展基址指针寄存器(extended base pointer) 其内存放一个指针,该指针指向系统栈最上面一个栈帧的底部。本来事实上已经通过内联汇编,修改过的值,为什么打印出来还是10呢。但是如果,将
int i=10; 前加 volatile 就不会发生这种情况了。跟踪汇编代码可以发现,凡是声明为 volatile
的变量,每次拿到的值都是从内存中直接读取的。
以下实验在 vs2012 release 环境下进行。
不加 volatile
int i=10; int a=i; tmp(a); 00D71273 push dword ptr ds:[0D73024h] 00D71279 mov ecx,dword ptr ds:[0D7303Ch] 00D7127F push 0Ah 00D71281 call dword ptr ds:[0D7302Ch] 00D71287 mov ecx,eax 00D71289 call dword ptr ds:[0D73028h] _asm{ mov dword ptr [ebp-4],80 00D7128F mov dword ptr [ebp-4],50h } int b=i; tmp(b); 00D71296 push dword ptr ds:[0D73024h] 00D7129C mov ecx,dword ptr ds:[0D7303Ch] 00D712A2 push 0Ah 00D712A4 call dword ptr ds:[0D7302Ch] 00D712AA mov ecx,eax 00D712AC call dword ptr ds:[0D73028h]
加了 volatile
tmp(a); 01201274 push dword ptr ds:[1203024h] volatile int i=10; 0120127A mov dword ptr [i],0Ah int a=i; 01201281 mov eax,dword ptr [i] tmp(a); 01201284 mov ecx,dword ptr ds:[120303Ch] 0120128A push eax 0120128B call dword ptr ds:[120302Ch] 01201291 mov ecx,eax 01201293 call dword ptr ds:[1203028h] _asm{ mov dword ptr [ebp-4],80 01201299 mov dword ptr [i],50h } int b=i; 012012A0 mov eax,dword ptr [i] tmp(b); 012012A3 push dword ptr ds:[1203024h] 012012A9 mov ecx,dword ptr ds:[120303Ch] 012012AF push eax tmp(b); 012012B0 call dword ptr ds:[120302Ch] 012012B6 mov ecx,eax 012012B8 call dword ptr ds:[1203028h]
由于编译器的极端优化,可以很明显的看到,在没有加 volatile 的情况下,甚至编译器是直接使用操作数 0Ah 进行运算的。
而在加了 volatile 的情况下,每次都是从 ptr [i] 中读取。而且在速度极端优化的情况下,
void tmp(int t) { cout<<t<<endl; }
也自动 inline 处理了。
但是这里也抛出一个问题,为什么是 [ebp-4] 修改的就是i的值,更奇怪的是,我如果如下这样写代码,那改的会是哪个变量的值呢:
#include<iostream>
usingnamespacestd;
void tmp(int t) { cout<<t<<endl; }
int main(int argc,char* argv[])
{
volatileint ic=12;
volatileint i=10;
int a=i;
volatileint ib=11;
tmp(a);
tmp(ib);
tmp(ic); //必须使用,如果不使用,编译器优化为使用同一块内存地址
_asm{
mov dwordptr [ebp-4],80
}
int b=i;
tmp(b);
return0;
}
为什么分配的总是 [ebp-4] 是复制给 a 的值呢?试验过,如果将 ic 赋值给 a,那 [ebp-4] 存放的值将会是 ic
阅读以上程序,注意以下几个要点:
(1)以上代码必须在release模式下考查,因为只有Release模式(严格说需要在速度最大优化 /O2)下才会对程序代码进行优化,而这种优化在变量共享的环境下容易引发问题。
(2)凡是需要被多个任务共享的变量(如可能被中断服务程序访问的变量、被其他线程访问的变量等),都应声明为 volatile 变量。而且为了提高执行效率,要减少对 volatile 不必要的使用。
(3)由于优化可能会将一些“无用”的代码彻底去除,所以,如果确实希望在可执行文件中保留这部分代码,也可以将其中的变量声明为 volatile:
int main(int argc,char* argv[]) { int s,i,j; for(i=0;i<100;++i) for(j=0;j<100;++j) s=5; return0; }
在生成 release 版本的程序时,由于循环体每次给 s 的值不变(简化为执行1次),或者说没有使用(1次都没有),但如果此时程序猿是希望循环拖延时间,写成 volatile 就可以了。
附录:问题
1)一个参数既可以是const还可以是volatile吗?解释为什么
是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2) 一个指针可以是volatile 吗?解释为什么
是的。尽管这并不很常见。一个例子是当一个中断服务子程序修该一个指向一个buffer的指针时。
3) 下面的函数有什么错误:
int square(volatileint*ptr) { return*ptr * *ptr; }
这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatileint*ptr) { int a,b; a = *ptr; b = *ptr; return a * b; }
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是 你所期望的平方值!正确的代码如下:
long square(volatileint *ptr) { int a; a = *ptr; returna * a; }
相关文章推荐
- C++关键字:mutable、volatile、explicit以及__based
- C++基本功:全面掌握const、volatile和mutable关键字
- C/C++中volatile关键字详解
- 讲讲C++中的volatile关键字[转]
- C++基本功: 全面掌握const, volatile 和 mutable关键字
- C++基本功:全面掌握const、volatile和mutable关键字
- 【转】在C/C++、Java中的volatile关键字
- C++基本功:全面掌握const、volatile和mutable关键字
- 讲讲C++中的volatile关键字
- [c/c++]关于volatile关键字的说明以及测试
- C++基本功:全面掌握const、volatile和mutable关键字
- C++基本功:全面掌握const、volatile和mutable关键字
- C++关键字:mutable、volatile、explicit以及__based mutable关键字
- C/C++中volatile关键字详解
- C++关键字:mutable、volatile、explicit以及__based
- C++ volatile 关键字
- C++ 关键字 volatile
- 讲讲C++中的volatile关键字
- C/C++中volatile关键字详解
- C++ volatile关键字