本以为理解const,其实那只是冰山一角

43 篇文章 0 订阅
18 篇文章 0 订阅

这里首先针对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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值