下面的代码在C和C++下编译,输出会是什么?
1 void main(void)
2 {
3 const int a = 0x00;
4 int *pi;
5
6 pi = (int *)&a;
7 *pi = 0xFF;
8
9 printf("*pi=%d,a=%d\n", *pi, a);
10 return 0;
11 }
注意,上面的代码第6行必须写为pi = (int *)&a;,才能编译通过,否则在gcc下会有警告,g++有编译错误(gcc版本6.5,低版本gcc不会):
$ gcc cnst.c -g -o cnst
cnst.c: In function ‘main’:
cnst.c:6:12: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
int *pi = &a;
$g++ cnst.cpp -g -o cnst
cnst.cpp: In function ‘int main()’:
cnst.cpp:6:12: error: invalid conversion from ‘const int*’ to ‘int*’ [-fpermissive]
int *pi = &a;
在Linux下分别保存为cnst.c和cnst.cpp,用GCC和G++编译,结果如下:
$ gcc cnst.c -o cnst
$ ./cnst
*pi=255,a=255
$ g++ cnst.cpp -o cnst
$ ./cnst
*pi=255,a=0
从执行结果来看,在C语言中,通过pi指针可以将int a的值修改为255,但是在C++中,似乎并不能通过pi指针修改const int a的值,why?
很奇怪?那就来看看汇编吧:
//C语言反汇编
#include <stdio.h>
int main()
{
400526: 55 push %rbp
400527: 48 89 e5 mov %rsp,%rbp
40052a: 48 83 ec 10 sub $0x10,%rsp
const int a = 0x00;
40052e: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%rbp)
int *pi = (int *)&a;
400535: 48 8d 45 f4 lea -0xc(%rbp),%rax
400539: 48 89 45 f8 mov %rax,-0x8(%rbp)
*pi = 0xFF;
40053d: 48 8b 45 f8 mov -0x8(%rbp),%rax
400541: c7 00 ff 00 00 00 movl $0xff,(%rax)
printf("*pi=%d,a=%d\n",*pi,a);
400547: 8b 55 f4 mov -0xc(%rbp),%edx
40054a: 48 8b 45 f8 mov -0x8(%rbp),%rax
//注意下面这行
40054e: 8b 00 mov (%rax),%eax
400550: 89 c6 mov %eax,%esi
400552: bf f4 05 40 00 mov $0x4005f4,%edi
400557: b8 00 00 00 00 mov $0x0,%eax
40055c: e8 9f fe ff ff callq 400400 <printf@plt>
return 0;
400561: b8 00 00 00 00 mov $0x0,%eax
}
//C++反汇编
#include <stdio.h>
int main()
{
400546: 55 push %rbp
400547: 48 89 e5 mov %rsp,%rbp
40054a: 48 83 ec 10 sub $0x10,%rsp
const int a = 0x00;
40054e: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%rbp)
int *pi = (int *)&a;
400555: 48 8d 45 f4 lea -0xc(%rbp),%rax
400559: 48 89 45 f8 mov %rax,-0x8(%rbp)
*pi = 0xFF;
40055d: 48 8b 45 f8 mov -0x8(%rbp),%rax
400561: c7 00 ff 00 00 00 movl $0xff,(%rax)
printf("*pi=%d,a=%d\n",*pi,a);
400567: 48 8b 45 f8 mov -0x8(%rbp),%rax
40056b: 8b 00 mov (%rax),%eax
//注意下面这行
40056d: ba 00 00 00 00 mov $0x0,%edx
400572: 89 c6 mov %eax,%esi
400574: bf 14 06 40 00 mov $0x400614,%edi
400579: b8 00 00 00 00 mov $0x0,%eax
40057e: e8 9d fe ff ff callq 400420 <printf@plt>
return 0;
400583: b8 00 00 00 00 mov $0x0,%eax
}
从上面的汇编可以看出,C++在printf输出const int a的地方,直接将a替换成了const int a的值“0”!虽然a在内存中的数据已经被修改为0xFF,但是printf输出的时候并不是输出a在内存中的值。注意C++中a的值同样被修改了,可以通过gdb来验证。
(gdb) l
1 #include <stdio.h>
2
3 int main()
4 {
5 const int a = 0x00;
6 int *pi = (int *)&a;
7
8 *pi = 0xFF;
9 printf("*pi=%d,a=%d\n",*pi,a);
10
(gdb) n
6 int *pi = (int *)&a;
(gdb) n
8 *pi = 0xFF;
(gdb) n
9 printf("*pi=%d,a=%d\n",*pi,a);
(gdb) n
*pi=255,a=0
11 return 0;
(gdb) p a
$1 = 255
(gdb)
编译器的这种行为叫做常量折叠,常量折叠就是将常量表达式计算求值,并用求得的值来替换表达式,放入常量表,在预编译阶段,编译器会将所有的常量进行求值,然后替换。这C++编译器提高性能的一种方式。