晚上無意翻看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而且是一個常量,也被儲存到常量表中,下面的流程就和第一個例子一樣了。