晚上无意翻看Bjarne Stroustrup的'The C++ Programming Language Special Edition'(英文版)第94页,章节5.4 Constants一节,看到这么一句原文'C++ offers the concept of a user-defined constant, a const, to express the notion that a value doesn't change directly.'字眼就在directly上,既然不能directly change,那我试试indirectly change。
问题就发现于这个indirectly change,代码如下:
#include <iostream>
int main() {
const int a = 2007; // 这是一个常量,我们'不能directly change'^_^
int *p = const_cast<int*>(&a); //我们换一种方法hijack
*p = 2008; //篡改
std::cout << "a = " << a << std::endl; //期待输出2008
std::cout << "*p = " << *p << std::endl;
std::cout << "&a = " << &a << std::endl;
std::cout << "p = " << p << std::endl;
return 0;
}
我首先在Windows上使用Mingw的g++编译,输出结果让我大惊失色:
a = 2007
*p = 2008
&a = 0x23ff74
p = 0x23ff74
原以为a应该被hijack了,结果a仍然原封未动;关键是后两行打印的a的地址和p的指向都是一个地方,难道C++对常量的保护如此之好,如此智能。不行,换一个平台试试,我又把源码搬到了Solaris上同样是g++编译器,输出结果一致。
百思不得其解后继续'咬文嚼字'的往下看该小节。突然发现这么一句话:'If the compiler knows every use of the const, it need not allocate space to hold it.'…'The common simple and common case is the one in which the value of the constant is known at compile time and no storage needs to be allocated.',左思又想,这么一来在某些时候a被当作类似宏的方式处理的,就如:std::cout << "a = " << a << std::endl;这里cout输出一个常量表达式,编译器估计直接将a替换成2007了,实际上就相当于std::cout << "a = " << 2007 << std::endl;而后的int *p = const_cast<int*>(&a);操作,这时就需要为a分配地址了。有人说a的输出操作是在分配地址之后,那为什么还输出2007呢,我们从编译器的角度看看,编译器在解析到const int a = 2007的时候发现这是一个常量,便将之首先记录到常量符号表中,而后在解析const_cast<int*>(&a)时为a在栈上分配内存,但是在走到输出a那块时首先引用到的还是常量符号表,而输出&a时,由于是取地址操作,所以就把前面分配的栈地址赋到这里了。
我们继续再看一个例子:
#include <iostream>
int main() {
int i = 2006;
const int a = i + 1;
int *p = const_cast<int*>(&a);
*p = 2008; //篡改
std::cout << "a = " << a << std::endl; //期待输出2008
std::cout << "*p = " << *p << std::endl;
std::cout << "&a = " << &a << std::endl;
std::cout << "p = " << p << std::endl;
return 0;
}
在这个例子中const int a = i + 1;用一个非常量表达式给常量a赋初值,按照Bjarne Stroustrup的说法,是需要给a分配内存了。这样我想编译器也许不会在常量符号表中给a留位置,在下面的a的打印输出时,a真的被hijack了。
输出结果:
a = 2008
*p = 2008
&a = 0x23ff70
p = 0x23ff70
再看一个例子:
#include <iostream>
int main() {
const int i = 2006;
const int a = i + 1;
int *p = const_cast<int*>(&a);
*p = 2008; //篡改
std::cout << "a = " << a << std::endl; //期待输出2008
std::cout << "*p = " << *p << std::endl;
std::cout << "&a = " << &a << std::endl;
std::cout << "p = " << p << std::endl;
return 0;
}
编译器在解析到const int i = 2006时首先将i作为常量保存到常量符号表中,在const int a = i + 1时实际上相当于const int a = 2006 + 1,编译器作优化,编译器直接得到a = 2007而且是一个常量,也被保存到常量表中,下面的流程就和第一个例子一样了。
评论