以下这段代码会输出什么?
const int i = 1;
int *j =(int*) &i;
*j = 22;
cout << &i << " " << j << endl;
cout << i << " " << *j << endl;
直觉上,&i 和 j 指向的是同一个地址,但打印出来 i 和 *j 却是不同的值。起初我以为是数据存储区不一样导致的问题,进一步分析后发现并非如此。
编译器对 const 变量的优化
既然源码最初就把它声明为 const,开发者的本意显然是不希望这个变量被修改。于是编译器可以直接把所有引用该变量的地方替换为立即数,从而省去一次内存读取,提高效率。经过这种优化之后,上面的代码实际上等价于:
const int i = 1;
int *j =(int*) &i;
*j = 22;
cout << &i << " " << j << endl;
cout << 1 << " " << *j << endl;
打印 i 的那一行被编译器直接换成了字面量 1,所以即使内存中的值已经通过指针改成了 22,输出的仍然是 1。这就是"同一个地址却打印出不同结果"的真正原因。
用 volatile 禁止这种优化
volatile 关键字会告诉编译器:这个变量随时可能发生变化,每次使用它都必须重新从内存中读取。加上 volatile 之后,编译器生成的可执行代码就会老老实实地从 i 的地址读数据,而不会再把它优化成立即数。
换句话说,volatile 可以禁止编译器做这种常量折叠式的优化。
new 出来的 const 指针不会被优化
不过对于用 new 动态分配得到的 const 值,情况就不一样了。这类值要到运行期才能确定,编译器在编译期无法做任何常量替换优化。例如下面这段代码:
int * const i = new int;
int *j = (int*)i;
*j = 22;
cout << i << " " << j << endl;
cout << *i << " " << *j;
这里 i 指向的内存内容是运行期才决定的,编译器在编译期根本无从得知它的值,自然也就无法把它替换成立即数。所以前面那个"疑惑"也就不复存在了。