[温故而知新] 关于C++的引用

好久没写博,平时都是把笔记写在有道云笔记里,最近国庆,克服懒癌来写写。距离上次写博,都快半年了,以后要勤快点了。话说霹雳布袋戏又要出新剧集了。

[温故而知新] 关于C++的引用_第1张图片

进入正题,写这篇文章的原因

之前看过几本C++的书,只记得C++的引用只是个所谓的“别名”,但是总感觉这东西很奇怪,当时也没具体去深究它,最近在看一本金山的一个哥们写的《深入应用C++ 11》,里面在聊到右值引用的时候,对引用有一句描述:

无论声明左值引用还是右值引用都必须立即进行初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的别名。

这里暂不深入讨论C++的左值引用和右值引用。
书中的这句话又让我想起之前的疑问,什么叫“不拥有所绑定对象的内存”?是说引用不占用内存??比如下面的代码:

#include 
class A{
public:
        int i;
        int j;
};

int main(){
        A * p = new A;
        A& q = *p;
        printf("%p %p \n",p,&q);

        return 0;
}

上面的代码编译后执行,输出结果为:
0x7f98d0404aa0 0x7f98d0404aa0
也就是看起来好像p和q是同一个东西,但是…

这里的q 是一个引用,那到底q占不占内存呢?看看汇编代码就知道了:

Disassembly of section .text:

0000000100000f20 <_main>:
   100000f20:   55                      push   %rbp
   100000f21:   48 89 e5                mov    %rsp,%rbp
   100000f24:   48 83 ec 20             sub    $0x20,%rsp
   100000f28:   b8 08 00 00 00          mov    $0x8,%eax
   100000f2d:   89 c7                   mov    %eax,%edi
   100000f2f:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
   100000f36:   e8 2f 00 00 00          callq  100000f6a <_main+0x4a>
   100000f3b:   48 8d 3d 50 00 00 00    lea    0x50(%rip),%rdi        # 100000f92 <_main+0x72>
   100000f42:   48 89 45 f0             mov    %rax,-0x10(%rbp)  #给p赋值
   100000f46:   48 8b 45 f0             mov    -0x10(%rbp),%rax  #
   100000f4a:   48 89 45 e8             mov    %rax,-0x18(%rbp)  #给q赋值
   100000f4e:   48 8b 75 f0             mov    -0x10(%rbp),%rsi
   100000f52:   48 8b 55 e8             mov    -0x18(%rbp),%rdx
   100000f56:   b0 00                   mov    $0x0,%al
   100000f58:   e8 13 00 00 00          callq  100000f70 <_main+0x50>
   100000f5d:   31 c9                   xor    %ecx,%ecx
   100000f5f:   89 45 e4                mov    %eax,-0x1c(%rbp)
   100000f62:   89 c8                   mov    %ecx,%eax
   100000f64:   48 83 c4 20             add    $0x20,%rsp
   100000f68:   5d                      pop    %rbp
   100000f69:   c3                      retq

从上面反汇编的代码看到,至少在我的机器上的g++,对于引用的实现,它就是个普通的变量,它也实实在在地占有栈内存。

那么如果让我写编译器来实现上面这段代码中的引用呢,实际上在这个例子中是可以做到不需要为q 分配额外的内存空间的,因为在编译的阶段,完全可以把用到 q 的地方,采用 栈基址+偏移量(bp + offset)的方式引用到变量p的地址,然后再偷偷解引用即可。

搜索了一下,有一篇类似探讨C++引用的文章 《深入分析C++引用》 这篇文章写的不错,结论也跟我猜的类似:

结论

C++标准并没有规定引用到底占不占内存。大部分编译器对于引用的实现,引用就是一个常量指针,是一种会被编译器自动解引用的指针。

把上面的代码转化一下:

A * p = new A;
A& q = *p;  => A* const m = p;

注意关键点!在于意识到自动解引用是编译器干的事!

我说C++很多知识点为什么那么难记,因为编译器干了好多事。

显然上面的引用的写法是比用常量指针的写法更加简洁。那么除了写起来简洁,还有啥优点?
1. 声明引用的同时必须对其进行初始化 。// 我想好处就是对于C++程序员来说,就可以不用像Java程序员那样蛋疼,动不动就Null Pointer Exception, 动不动就得写个 if(null != obj)。引用确实是个好东西。
2. 引用声明结束后,不能作为别的变量的别名。 // 我想好处就是限制程序员把指针搞来搞去把自己都给搞晕了。

综上,C++的引用是个好东西,只不过得花点时间理解它。

你可能感兴趣的:(C/C++)