底层剖析引用实现原理(引用是占有内存空间的)


正如《C++ primer》中所述,“引用即别名,它并非对象,相反地,它只是一个已经存在的对象所起的另外一个名字。”刚学习C++的时候,觉得引用就是一个别名,并不会占有内存。剖析了引用的底层实现原理之后,才发现这是错误的,引用也是会占有内存的,且底层是通过指针来实现的。

一、底层剖析引用实现原理

以下是一段对引用和源变量取地址的代码,一起看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
using namespace std;
int main()
{<!-- -->
    int a = 10;
    int &b = a;

    cout << "a的地址为:" << &a << endl;
    cout << "b的地址为:" << &b << endl;

    system("pause");
    return 0;
}

运行结果:
在这里插入图片描述
我们发现,a和b的地址是一样的,看来引用是不占用内存的。实际上,&b并不能得到b的地址,而得到的还是变量a的地址。原因就是,引用在C++中是通过一个常指针来实现的,即&b=a实际上等价于int* const b=&a,而编译器会把&b编译为:&(*b),那么得到的自然就是a的地址,所以我们会看到&a、&b得到的地址是一样的。但是一定要注意,&b并不是b的地址。

为了佐证上述原因,我们在上述代码中加入一个常指针int* const c=&a,即:

1
2
3
    int a = 10;
    int &b = a;
    int* const c = &a;

利用vs2013编译器将源代码转换为反汇编,则引用b和常指针c实现的反汇编语言如下:

1
2
3
4
5
6
7
8
    //int a = 10;  //源代码
01305E88  mov         dword ptr [a],0Ah    //将10复制到变量a的地址空间
    //int &b = a;  //源代码
01305E8F  lea         eax,[a]              //将变量a的地址复制到寄存器eax中
01305E92  mov         dword ptr [b],eax    //从寄存器eax中取出地址赋值给变量b的地址空间
    //int* const c = &a;  //源代码
01305E95  lea         eax,[a]              //将变量a的地址复制到寄存器eax中
01305E98  mov         dword ptr [c],eax    //从寄存器eax中取出地址赋值给变量c的地址空间

从汇编语言可以看到,源代码int &b = a和int* const c = &a的汇编代码是一模一样的,均为:将变量a的地址复制到寄存器eax中,再将eax中的值赋值给变量b或者c。这也证明了:引用是通过常指针来实现的。那么,为什么是常指针而不是普通指针呢?因为引用的对象是不能改变的,若为普通指针则可以改变指向的对象,但用常指针实现就能保证指向的对象是不变的。

但是,我们会发现由于编译器改变了引用的取地址含义,那么想获取引用的真实地址好像就很难了,我们能看到的自然只有与引用对象相同的地址。

通过上面的分析,希望读者能更正一个认识: 引用同指针一样占有内存空间的,且内存的大小与指针一样,即32系统上是4个字节,64位系统上是8个字节。 这一点非常重要,很多人写的博客都说“引用不占内存”,这是错误的。

分析了引用的底层实现原理之后,引用与指针的区别也就一目了然了,下面就总结一下。

二、引用与指针的区别

引用的安全性比指针好:引用的对象不能改变,安全性好;但指针指向的对象是可以改变的,不能保证安全性。
引用的使用比指针方便:引用实际上是封装好的指针解引用(即b->*b),可以直接使用;但指针需要手动解引用,使用更麻烦;
引用的级数比指针少:引用只有一级,不能多次引用;但指针的级数没有限制,即一个指针可以指向另一个指针。