Blog

编译器对const常量的处理

September 25, 2010 Post in 程序设计

先来看一段代码,这是今天师兄贴给的。

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main()
{
	const int i = 10;
	int* pi = const_cast<int*>(&i);
	*pi = 100;
	printf("%d %d\n", i, *pi);
	return 0;
}

代码运行的结果是什么呢?按道理讲,i是一个const类型的常量,pi指向了存储i的内存空间的地址,然后通过pi改变了内存中的这个值,那么,结果应该是“100 100”?
可以自己跑了这段代码之后发现,输出的结果是“10 100”,这是为什么呢?

很明显,在printf输出的时候,i的值依然为最初的10,而不是改变之后的值。难道pi没有改变内存中存储的数值?于是按如下方法修改代码,在使用pi赋值以后,再用i的地址取值。
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main()
{
	const int i = 10;
	int* pi = const_cast<int*>(&i);
	*pi = 100;
	int* pi2 = const_cast<int*>(&i);
	printf("%d %d %d\n", i, *pi, *pi2);
	return 0;
}

这样得到的结果是“10 100 100”。也就是说,*pi = 100执行正确,也确实改变了内存中的值。但是printf输出的i依然是10,那么就可以断定,问题出在编译器的环节。解决这种问题,最简单的方法就是看一下编译器生成汇编。直接 g++ -S 一下得到第一段程序代码的汇编如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.file	"c.cpp"
	.def	___main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
LC0:
	.ascii "%d %d\12\0"
	.text
.globl _main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
LFB8:
	pushl	%ebp
LCFI0:
	movl	%esp, %ebp
LCFI1:
	andl	$-16, %esp
LCFI2:
	subl	$32, %esp
LCFI3:
	call	___main
	movl	$10, 24(%esp)
	leal	24(%esp), %eax
	movl	%eax, 28(%esp)
	movl	28(%esp), %eax
	movl	$100, (%eax)
	movl	28(%esp), %eax
	movl	(%eax), %eax
	movl	%eax, 8(%esp)
	movl	$10, 4(%esp)
	movl	$LC0, (%esp)
	call	_printf
	movl	$0, %eax
	leave
	ret
LFE8:
	.def	_printf;	.scl	2;	.type	32;	.endef

可以看到,第25到30行是调用printf函数。其中第25、26、27三行是取出pi指向的空间的数值放入堆栈,第28行是将数字10放入堆栈。看到这里就知道为什么printf会输出10了。在代码中取i值的地方,在编译的时候,编译器直接将其替换为i指向的常量的值。注意,这是在编译时替换的,也就是说跟内存中那个常量的位置存的是什么数值没有关系。而const_cast则是让编译器把常量的标识符当作变量对待。
不过正常人写代码一般是不会遇到这种问题的,常量就该尽职的做常量,不要变来变去的,要不直接用变量就好了~不过还是要谨记,防止出现不必要的麻烦,要不出了问题还真不好解决…

标签: c++, const