清风竹林

ぷ雪飘绛梅映残红
   ぷ花舞霜飞映苍松
     ----- Do more,suffer less

const灵异现象

const灵异现象

版本:0.1

最后修改:2010-11-22

撰写:李现民


概述

constc++中意味着“不可改变”,但在有些情况下我们可以“合法”地绕过编译器去修改一些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,也不是编译器进行了优化的问题。事实上,在各版本的VCg++下的运行结果均是如此。

以下是VC2008debug版本的汇编代码:

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;
}


VC6Release版本运行结果如下:

match
match
watch

VC2008Release版本运行结果如下:

watch
watch
watch


容易看出:这段代码的运行结果决定于编译器,因为我们改写了不应该被改写的常量数据。更根本的原因是:由于编译器优化,str1str2实际上指向的是同一份”watch”字符串

这还带出了另一件事:尽管str1的声明中不带const,但它所指向的字符串数据隐含是const类型的

注意:这段代码只有Release版本才能顺利执行,Debug版版本运行时会得到一个Access violation




posted on 2010-11-22 15:16 李现民 阅读(2296) 评论(14)  编辑 收藏 引用 所属分类: 语法试炼

评论

# re: const灵异现象 2010-11-22 15:33 airtrack

google常量折叠  回复  更多评论   

# re: const灵异现象 2010-11-22 15:40 李现民

@airtrack
受教了, 呵呵, 第一次听说这个词, 谢谢  回复  更多评论   

# re: const灵异现象 2010-11-22 18:57 冬瓜

const仅是编译器的警告!有很多做法都可以绕过const.  回复  更多评论   

# re: const灵异现象 2010-11-22 19:37 李现民

@冬瓜
可不仅仅是这样的,对于修饰为const的变量,编译器可能会去做一些处理,表现在有无const,最终生成的汇编代码很可能是不同的  回复  更多评论   

# re: const灵异现象[未登录] 2010-11-23 09:34 vincent

@李现民
无非就是const的可能放在只读数据段吧?
然后对于这个段设置只读?

是这样吗?  回复  更多评论   

# re: const灵异现象 2010-11-23 13:01

非正常代码,得到的结论自然非正常了。  回复  更多评论   

# re: const灵异现象 2010-11-23 17:41 classyk

一定要更改就使用volatile const
但不是每一个编译器都支持。  回复  更多评论   

# re: const灵异现象 2010-11-23 18:16 李现民

@vincent
在默认情况下,其实不是这样的。
const 修饰内置数据类型时,比如const int a= 10; 则a是一个编译期常量,因此你可以进一步定义int b[a]= {0}; 在编译意味着它没有内存地址(这是一个运行期的概念),因此并不存在放置于只读数据段的现象。

只所以说是“默认情况”,是因此在更复杂的情况下const常量对象是需要分配内存的,比如说文章中出现的取其地址的时候,再比如定义更加复杂的集合对象的时候。  回复  更多评论   

# re: const灵异现象 2010-11-23 18:20 李现民

@classyk
我的理解:volatile const其实是变量可以不可预期的被程序以外的环境所改变,而对于相同的一段程序内部而言,这样声明并不能使你有更好的方式可以修改此变量的值  回复  更多评论   

# re: const灵异现象[未登录] 2010-11-24 19:10 vincent

@李现民
呵呵,是这样的,是我粗心忘了这个:)  回复  更多评论   

# re: const灵异现象 2010-11-24 22:45 fool

字符串放在字符串常量区中
强烈反对,“恶心”编译器的代码
既然约定const为什么不老实呢

const_cast,有用但不能滥用  回复  更多评论   

# re: const灵异现象 2010-11-25 09:25 李现民

@fool
支持你一下, 哈哈
  回复  更多评论   

# re: const灵异现象 2010-11-29 09:08 CR苏杭

编译器会在调用const的那个量的地方直接用字面值替代。
vs2010也输出
watch
watch
watch  回复  更多评论   

# re: const灵异现象 2010-12-01 12:49 海枫

>> 关键其实在于:const_cast所操作的目标是否为基础数据类型(char, int, float, double等),如果是类(或结构体)对象则又将是另一番情形。

说错了,关键是编译器做了优化,每次读a是,不一定是从内存中读取的,在printf的时候,就是从寄存器中读取的。为什么这样呢?为a是const的,是不会变的,所以它直接读寄存器就可以了。这是const特性保证的。 这不是什么const 灵异,请忽将一个正常的行为说得那么可怕。

如果你不想编译器优化a,那么可以写成下面这样。

void foo()
{
volatile 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);
}

请不要跟我说volatile 和const不能同时修饰一个变量,那是因为你想不通而已。  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理