const灵异现象
版本:0.1
最后修改:2010-11-22
撰写:李现民
概述
const在c++中意味着“不可改变”,但在有些情况下我们可以“合法”地绕过编译器去修改一些const数据,比如const_cast就可以剥离一个对象的const属性。然而,我们这样做在多大程度上是“合理”的,却因不同的问题而论,也许一不小心,你就可能掉入陷阱之中。
以下问题,我只分析,不说话,请各位看官自己判断。
当目标是一个常数
这件事源于在网上看到的一篇文章,其来源已经不可考,但大意是:就如下C++程序,其输出是什么:
void
foo()
{
const
int
a =
1;
int*
p =
const_cast<int*>(&a);
*p =
2;
printf("
a= %d\n *p= %d\n &a= %x\n p= %x \n\n",
a,
*p,
&a,
p);
}
我在VC2008下的实测结果为:
a = 1
*p = 2
&a = 12ff6c
p = 12ff6c
好了,问题出现:明明p所指向的就是变量a,但为何打印其值时a!=*p?
这并非是我用错了const_cast,也不是编译器进行了优化的问题。事实上,在各版本的VC与g++下的运行结果均是如此。
以下是VC2008下debug版本的汇编代码:
const
int a = 1;
0041146E
mov dword ptr [a],1
int*
p = const_cast<int*>(&a);
00411475
lea eax,[a]
00411478
mov dword ptr [p],eax
*p =
2;
0041147B
mov eax,dword ptr [p]
0041147E
mov dword ptr [eax],2
printf("
a= %d\n *p= %d\n &a= %x\n p= %x \n\n", a, *p, &a, p);
00411484
mov esi,esp
00411486
mov eax,dword ptr [p]
00411489
push eax
0041148A
lea ecx,[a]
0041148D
push ecx
0041148E
mov edx,dword ptr [p]
00411491
mov eax,dword ptr [edx]
00411493
push eax
00411494
push 1
00411496
push offset string " a=\t%d\n *p=\t%d\n &a=\t%x\n
p=\t%x \n\n"... (415808h)
0041149B
call dword ptr [__imp__printf (419318h)]
从printf()的四个参数入栈过程中我们可以看出:指针p的确指向变量a了,而变量a处的数值也的确被改写成2了,问题是:当压入a的值的时候,编译器直接压入了其原始数值1。
关键其实在于:const_cast所操作的目标是否为基础数据类型(char,
int, float, double等),如果是类(或结构体)对象则又将是另一番情形。
当修改字符串常量
这个问题最早见于一篇文章《Solmyr的小品文系列之一:字符串放在哪里?》,在这里我只不过转述一二。
代码如下:
void
foo()
{
char*
str1 =
"watch";
const
char*
str2 =
"watch";
char
str3[] =
"watch";
str1[0] =
'm';
std::cout<<
str1
<< std::endl
<< str2
<< std::endl
<< str3
<< std::endl;
}
VC6中Release版本运行结果如下:
match
match
watch
VC2008中Release版本运行结果如下:
watch
watch
watch
容易看出:这段代码的运行结果决定于编译器,因为我们改写了不应该被改写的常量数据。更根本的原因是:由于编译器优化,str1与str2实际上指向的是同一份”watch”字符串。
这还带出了另一件事:尽管str1的声明中不带const,但它所指向的字符串数据隐含是const类型的。
注意:这段代码只有Release版本才能顺利执行,Debug版版本运行时会得到一个Access
violation 。