程序员iOS Developer代码改变世界

朝花夕拾:const关键字的要点和实现机制

2016-11-23  本文已影响296人  Maru
C Programming Language.png

0x00 最初的起点

首先是一道烂大街的题目:以下四个变量有什么区别?

Problem.png

答案很简单:

1. v1是一个指针常量,即指针所指向的值不可改变,但是指针所指向的地址可以改变。
2. v2和v1相同。
3. v3是一个常量指针,即指针所指向的内存地址不可改变。
4. v4是一个指向常量的常量指针,即无论是指向的内存地址,还是内存地址中的值都是不可改变的。

可以看到,这个基本上就是依靠记忆的东西,并没有什么理解上的难点,只是记住之后没过一段时间可能就又混淆了。

记忆方案一

1.如果const位于的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;
2.如果const位于
的右侧,则const就是修饰指针本身,即指针本身是常量。

恩... 似乎很有规律的样子,但是我记性很差,这种规则对我来说还是太绕,过一段时间就不确定会不会记反了。

记忆方案二

Bjarne在他的The C++ Programming Language里面给出过一个助记的方法:

把一个声明从右向左读。

char * const cp; ( * 读成 pointer to )
cp is a const pointer to char

const char * p;
p is a pointer to const char;

恩。这样确实大大的降低了记混的情况了,但是虽然这种方法已经很简单了,但是对于英语不是母语的人来说,还是需要绕一层的,那么有什么更加简单的记忆方法吗?

记忆方案三

const修饰前面的关键字,只有当const开头时才修饰后面的关键字

即:当const前面是char int之类的类型关键字时,表示修饰的是指针所指向的内容;而当前面的是指针运算符(*)的时候,那么表示修饰的是该指针;当const位于表达式首位时,请参考第一条。

0X01实现机制

其实const关键字并没有多么复杂的实现方法,只是在编译器的层面做了modify的限制,而const变量和非const变量在runtime期间其实并无卵分别,最好的证据就是编译过程中的汇编代码了,证据如下:

首先我们有两段简单到妈都不认得的C++代码:

代码一

#include <iostream>
using namespace std;

int main()
{
    int a = 100;
    return 0;
}

代码二


#include <iostream>
using namespace std;

int main()
{
    const int a = 100;
    return 0;
}


这两段代码唯一的区别就是,一个int变量有const修饰,而另一个int变量没有const修饰。OK,接下来我们编译成汇编,使用下面的命令:

g++ -S demo.cpp

编译之后得到的汇编代码对比图如下所示:

汇编代码对比.png

我们可看到,两者并无什么不同,也就是说,汇编并不会对const的变量做什么特殊的处理,const只是在编译的层面给程序做了限制。

但是,这里又有一个问题,当我们把代码修改成下面那样的时候:

代码一

#include <iostream>
using namespace std;

int main()
{
    int a = 100;
    cout << a << endl;
    return 0;
}

代码二


#include <iostream>
using namespace std;

int main()
{
    const int a = 100;
    cout << a << endl;
    return 0;
}


这里的代码与上面的代码的区别无非是,访问了a的值,按照上面的理论,编译后的代码应该没有不同,但是世事难料,对比图如下:

g++编译下对比.png

我们可以看到竟然还是存在差别的,但是根据查阅资料和个人猜测,我认为可能的原因是:const的变量存储的时候确实没有特殊处理,但是在取值时,并不会访问const变量的内存地址,而是通过一个全局符号表(symbol table)替换了该值,所以才会出现这样的汇编代码的差别。

0X02 最后说的话

说到底还是不懂汇编,有空也可以研究一下,如果有汇编大神路过,也请不吝赐教!

上一篇下一篇

猜你喜欢

热点阅读