这里首先针对c中const来研究一下,对于c++中在后面的博客中分析。
关于教科书里面的一些问题,比如初始化呀什么的,这里就不列举的,下面看看几个问题。
第一个问题
看下面这个代码
编译有警告
看看运行结果
这是与我之前的理解又有差异了,在之前的理解是,a的值是不变的,因为这是常量折叠的问题,然而在linux gcc上的运行结果让我困惑了。
如果换到vs2008上,如下同样代码
编译却是出错的
因为这确实是错误的,
const 变量取地址得到的是const 类型的指针,赋值给非cosnt类型,这是不符合c语法的,
这一点是vs编译器和gcc编译器的一个差别,
在前面一片博客研究过,利用强制转换,将这个错误消除掉,
看看vs2008
编译结果
没有问题
运行结果
这结果就是上一篇博客认为的常量折叠的结果,
然而,不要想当然的认为就是这样,
在linux gcc下看看
同样的代码
编译结果没有警告也没有错误
运行结果
所以这就是你想不到的地方
要解释这个问题,需要回到汇编代码来看看,
vs2008情况下的汇编代码已经研究过,在博客【分析编译器对C关键字的处理】
现在看看gcc下利用objdump 反汇编出来的代码
int main()
{
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 48 83 ec 10 sub $0x10,%rsp
const int a=10;
400535: c7 45 f4 0a 00 00 00 movl $0xa,-0xc(%rbp)
int * p;
p=(int*)&a;
40053c: 48 8d 45 f4 lea -0xc(%rbp),%rax
400540: 48 89 45 f8 mov %rax,-0x8(%rbp)
*p=12;
400544: 48 8b 45 f8 mov -0x8(%rbp),%rax
400548: c7 00 0c 00 00 00 movl $0xc,(%rax)
printf("%d\n",a);
40054e: 8b 45 f4 mov -0xc(%rbp),%eax
400551: 89 c6 mov %eax,%esi
400553: bf 04 06 40 00 mov $0x400604,%edi
400558: b8 00 00 00 00 mov $0x0,%eax
40055d: e8 ae fe ff ff callq 400410 <printf@plt>
printf("%d\n",*p);
400562: 48 8b 45 f8 mov -0x8(%rbp),%rax
400566: 8b 00 mov (%rax),%eax
400568: 89 c6 mov %eax,%esi
40056a: bf 04 06 40 00 mov $0x400604,%edi
40056f: b8 00 00 00 00 mov $0x0,%eax
400574: e8 97 fe ff ff callq 400410 <printf@plt>
}
从上面的反汇编代码,很清楚的看到,在gcc中并没有使用常量折叠这个方法来处理const,
printf(“%d\n”,a);
40054e: 8b 45 f4 mov -0xc(%rbp),%eax
对a的取值是,还是到a对应的栈空间里面去取,而不是直接替换成常数。
然而如果再换成g++编译器,同样上面的代码
编译运行结果
结果是不一样的,好神奇,再看看反汇编
int main()
{
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 48 83 ec 10 sub $0x10,%rsp
const int a=10;
400535: c7 45 f4 0a 00 00 00 movl $0xa,-0xc(%rbp)
int * p;
p=(int*)&a;
40053c: 48 8d 45 f4 lea -0xc(%rbp),%rax
400540: 48 89 45 f8 mov %rax,-0x8(%rbp)
*p=12;
400544: 48 8b 45 f8 mov -0x8(%rbp),%rax
400548: c7 00 0c 00 00 00 movl $0xc,(%rax)
printf("%d\n",a);
40054e: be 0a 00 00 00 mov $0xa,%esi
400553: bf 04 06 40 00 mov $0x400604,%edi
400558: b8 00 00 00 00 mov $0x0,%eax
40055d: e8 ae fe ff ff callq 400410 <printf@plt>
printf("%d\n",*p);
400562: 48 8b 45 f8 mov -0x8(%rbp),%rax
400566: 8b 00 mov (%rax),%eax
400568: 89 c6 mov %eax,%esi
40056a: bf 04 06 40 00 mov $0x400604,%edi
40056f: b8 00 00 00 00 mov $0x0,%eax
400574: e8 97 fe ff ff callq 400410 <printf@plt>
}
特别这句代码
printf(“%d\n”,a);
40054e: be 0a 00 00 00 mov $0xa,%esi
直接将常数10赋值给esi用于打印,
分析到这里似乎是这样的c编译器对于const的处理和c++编译器的处理是不一样的。
需要知道gcc编译器是如何处理const
下面一段代码,用于测试gcc对const的处理
#include<stdio.h>
int main()
{
const int a=10;
int *p;
//p=(int*)&a;
//*p=12;
printf("%d\n",a);
//printf("%d\n",*p);
}
反汇编这段代码
int main()
{
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 48 83 ec 10 sub $0x10,%rsp
const int a=10;
400535: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp)
int *p;
//p=(int*)&a;
//*p=12;
printf("%d\n",a);
40053c: 8b 45 fc mov -0x4(%rbp),%eax
40053f: 89 c6 mov %eax,%esi
400541: bf e4 05 40 00 mov $0x4005e4,%edi
400546: b8 00 00 00 00 mov $0x0,%eax
40054b: e8 c0 fe ff ff callq 400410 <printf@plt>
//printf("%d\n",*p);
}
其中
printf(“%d\n”,a);
40053c: 8b 45 fc mov -0x4(%rbp),%eax
依旧没有常量折叠
再看看g++中
同样上面这段代码
int main()
{
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 48 83 ec 10 sub $0x10,%rsp
const int a=10;
400535: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp)
int *p;
//p=(int*)&a;
//*p=12;
printf("%d\n",a);
40053c: be 0a 00 00 00 mov $0xa,%esi
400541: bf e4 05 40 00 mov $0x4005e4,%edi
400546: b8 00 00 00 00 mov $0x0,%eax
40054b: e8 c0 fe ff ff callq 400410 <printf@plt>
//printf("%d\n",*p);
}
其中
printf(“%d\n”,a);
40053c: be 0a 00 00 00 mov $0xa,%esi
使用了常量折叠
突发奇想, 关键字volatile是好像常量折叠的克星
将上面的一句语句换成
volatile const int a=10;
然后运行反汇编看看
int main()
{
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 48 83 ec 10 sub $0x10,%rsp
volatile const int a=10;
400535: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp)
int *p;
//p=(int*)&a;
//*p=12;
printf("%d\n",a);
40053c: 8b 45 fc mov -0x4(%rbp),%eax
40053f: 89 c6 mov %eax,%esi
400541: bf e4 05 40 00 mov $0x4005e4,%edi
400546: b8 00 00 00 00 mov $0x0,%eax
40054b: e8 c0 fe ff ff callq 400410 <printf@plt>
//printf("%d\n",*p);
}
其中
printf(“%d\n”,a);
40053c: 8b 45 fc mov -0x4(%rbp),%eax
常量折叠就消失了。
上面分析之后,之前的认识又推翻了,重新建立对此 常量折叠的概念。
下面看看有一个问题
这段代码本意很简单,指针p1指向结构体,并且指针对此只读,然后指向下一个结构体。
然而,看看gcc编译结果
是很出乎意料的,p1成了只读变量,
这个和
const int* p;
int* const p;
不一样了,
在一篇文章中是这样,解释的
这是因为能把 (struct s *)重定义为一个整体,const遇到整体的类型定义会直接将这个整体忽略,也就是对于const int * p和 int * const p以及const int p和 int const p,编译器会把int忽略,得到 const * p和* const p,以及const p。
这里就有个问题,typedef和define实现的变量类型。
如下
编译没有问题。
从中也可以看出两者的区别
所以分析到这,const 的处理远远不仅仅是教科书上讲的那样。
勘误说明:
上面在讲下面段代码时,
#include<stdio.h>
int main()
{
const int a=10;
int *p;
p=&a;
*p=12;
printf("%d\n",a);
printf("%d\n",*p);
}
说vs下面是编译错误的,有个问题没有说清,虽然是c代码,我却在vs下面定义的cpp后缀,如果修改成c后缀就没有问题了。突然想起哪本书上讲的c++的编译器比c的要求严格多。非常感谢小伙伴astrotycoon的提醒。他的博客链接是http://blog.csdn.net/astrotycoon