为了直观的感受编译器为程序所做的编译优化,我们通过以下的C++程序来进行演示(只能体现编译优化的一小部分hh~)。
请大家预测一下下面代码的输出结果
#include <iostream>
int main(void)
{
const volatile int local = 10;
int* ptr = (int*)&local;
printf("Initial value of local : %d \n", local);
*ptr = 100;
printf("Modified value of local: %d \n", local);
return 0;
}
可能大家会想,const修饰的变量怎么还能被修改了呢?(或者是这样想,这不就是通过指针把指向地址的内容给修改了嘛,没有难度呀!)
好,下面我们对此做出解释:
(1)对于持这种观点的读者(const修饰的变量怎么还能被修改了呢?),我们对程序做以下修改(增加一行 local = 20)。
#include <iostream>
int main(void)
{
const volatile int local = 10;
// const int local = 10;
int* ptr = (int*)&local;
printf("Initial value of local : %d \n", local);
*ptr = 100;
local = 20;
printf("Modified value of local: %d \n", local);
return 0;
}
然后再进行编译,结果如下。
这说明const还是起作用的,编译报错,说表达式必须是可修改的左值,而现在的这个左值是只读的。而我们这个代码之所以能够修改local,就是因为我们通过使用一个指针ptr(将经过int *类型转换的&local赋给它)的缘故。
(2)对于持这种观点的读者(这不就是通过指针把指向地址的内容给修改了嘛),我们再对代码进行修改(待我把volatile去掉,再运行一下)
#include <iostream>
int main(void)
{
// const volatile int local = 10;
const int local = 10;
int* ptr = (int*)&local;
printf("Initial value of local : %d \n", local);
*ptr = 100;
printf("Modified value of local: %d \n", local);
return 0;
}
嘿嘿,这个结果是不是比较出乎意料呢?为啥捏?
下面我们来看看volatile关键字的定义
volatile关键字旨在防止编译器对对象进行任何优化,这些对象可能被修改,而这种修改编译器无法确定。
声明为volatile的对象从优化中被省略,因为它们的值可以随时通过当前代码范围之外的代码进行更改。 系统总是从内存位置读取volatile对象的当前值,而不是在请求时将其值保存在临时寄存器中,即使先前的指令要求从同一对象中获取值。
由于访问内存比访问寄存器差不多要慢一个数量级,所以常见的优化有,如果编译器认为一个变量的值在一段时间内不会被修改,那么它就把这个变量从内存保存到寄存器,然后需要用的时候从寄存器(而不是内存)进行读取。
如果一个变量被声明为volatile,这就是在提醒编译器,这个变量不知道啥时候就要被修改了,所以你不要保存它,每次都要读取最新的值。因此,上述代码中的local变量有volatile修饰的时候,一旦被修改,立刻会被反映出来,但如果没有volatile修饰,编译器会认为,这个local变量是const修饰的(是个常量),那么我就没必要去读内存了,直接从寄存器中把这个值读出来就好了,虽然我们此时利用指针ptr修改了内存中的值,但是print的时候,仍然是读取的寄存器中的10。而用volatile就不会出现这种情况,它总是会去读内存中的值。
为了更加深入的了解volatile的作用,我们对上述程序做以下处理:
● 将不用volatile修饰local变量的C++程序(上面的第三个代码块),编译后生成的可执行文件,记为test。
● 将使用volatile修饰local变量的C++程序(上面的第一个代码块),编译后生成的可执行文件,记为testv。
分别对这两个可执行文件反汇编,看看在汇编代码中是如何体现这种区别的。
我们可以看到,在没有volatile修饰的情况下,编译器对程序做了优化,直接把变量local变成0xa(十进制10),所有用到local的地方甚至都没有去寄存器中取,而是直接把0xa(十进制10)拿过来用了。
而在有volatile修饰的情况下,还是老老实实地从内存中取到eax寄存器,然后再把eax寄存器中的值放到esi,所以print出来的是最新的值。