一、引用的本质是什么

说到引用,一般C++的教材中都是这么定义的:

1,引用就是一个对象的别名。

2,引用不是值不占内存空间。

3,引用必须在定义时赋值,将变量与引用绑定。

那你有没有想过,上面的定义正确吗?编译器是如何解释引用的?

这里先给出引用的本质定义,后面我们再进一步论证。

1,引用实际是通过指针实现的。

2,引用是一个常量指针。

3,引用在内存中占4个字节。

4,在对引用定义时,需要对这个常量指针初始化。

二、探究本质

我们从最简单的变量的定义开始,看编译器会做哪些事情。

int var = 42;
mov dword ptr [var],2Ah // 对应汇编代码

上面语句申请了一块内存空间,占4个字节,存放了一个int型的变量。内存里放的是42的二进制码。

汇编代码向我们表达的意思就是把42写入以var为地址的内容区域。var有点像我们理解上的指针,只是编译器并没有把它抽象出来,而是让我们更表象的理解:申请一个变量,它的值为42。

那么var这个变量名放在哪呢?

我们知道程序如果访问内存里的数据,需要通过地址来进行访问,所以上面的代码在经过编译器生成目标代码时,用存放42的地址了所有的var,所以结论时,目标文件中不存在var,所以变量名本身是不占内存的。

而我们知道,引用是变量的一个别名。那么,从这很多人会联想到,引用会不会也只是一个名字而已,编译器在生成目标代码的时候,会用实际地址替换引用呢?

答案并非这样!

那我们接下来看看,当我们定义一个引用时,发生了什么:

1     int var = 42;
2 01303AC8 mov dword ptr [var],2Ah
3 int& refVar = var;
4 01303ACF lea eax,[var]
5 01303AD2 mov dword ptr [refVar],eax

上面的代码显示,当定义一个引用时,编译器将var的地址赋给了以refVar为地址的一块内存区域。也就是说refVar其实存放的是var的地址。

这让我们联想到了指针,那么我们看看定义一个指针是发生了什么:

1     int var = 42;
2 01213AC8 mov dword ptr [var],2Ah
3 int* ptrVar = &var;
4 01213ACF lea eax,[var]
5 01213AD2 mov dword ptr [ptrVar],eax

没错,没有任何差别,定义一个引用和一个指针的汇编代码完全一致!

三、const哪里去了

相信从上面的分析时,你可能已经相信了,引用实际上就是一个指针。那么为什么说引用是一个常量指针呢,在目标代码里有什么体现呢?

这个问题其实要从C++底层机制谈起,C++为我们提供的各种存取控制仅仅是在编译阶段给我们的限制,也就是说编译器确保了你在完成任务之前的正确行为,如果你的行为不正确,那么编译器就是给你在编译时提示错误。所谓的const和private等在实际的目标代码里根本不存在,所以在程序运行期间只要你愿意,你可以通过内存工具修改它的任何一个变量的值。

这也就解释了为什么上面的两段代码中引用和指针的汇编代码完全一致。

C++设计引用,并用常量指针来从编译器的角度实现它,目标是为了提供比指针更高的安全性,因为常量指针一旦与变量地址绑定将不能更改,这样降低了指针的危险系数,它提供了一种一对一的指针。

但是你觉得使用引用就安全了吗?它同样会有与使用指针一样的问题

1 int *var = new int(42);
2 int &ref = *var;
3 delete var;
4 ref = 42;
5 return 0;

上面这段代码就很不安全,因为ref引用的内存区域不合法。

为了进一步验证引用与指针在本质上的相同,我们看当引用作为函数参数传递时,编译器的行为:

 1 void Swap(int& v1, int& v2);
2 void Swap(int* v1, int* v2);
3
4 int var1 = 1;
5 00A64AF8 mov dword ptr [var1],1
6 int var2 = 2;
7 00A64AFF mov dword ptr [var2],2
8 Swap(var1,var2);
9 00A64B06 lea eax,[var2]
10 00A64B09 push eax
11 00A64B0A lea ecx,[var1]
12 00A64B0D push ecx
13 00A64B0E call Swap (0A6141Fh)
14 00A64B13 add esp,8
15 Swap(&var1, &var2);
16 00A64B16 lea eax,[var2]
17 00A64B19 push eax
18 00A64B1A lea ecx,[var1]
19 00A64B1D push ecx
20 00A64B1E call Swap (0A61424h)
21 00A64B23 add esp,8

上面代码再次证明了,引用与指针的行为完全一致,只是编译器在编译时对引用作了更严格的限制。

四、引用占多大的内存空间

因为在在表达式中,使用引用实际上就像使用变量本身一样,所以直接用sizeof是得不到引用本身的大小的。

double var = 42.0;

double& ref = var;

cout << sizeof var << endl; // print 8

cout << sizeof ref << endl; // print 8

我们可以通过定义一个只含有引用的类来解决这个问题:

1 class refClass{

2 private:

3 double& ref;

4 public:

5 refClass(double var = 42.0) :ref(var){}

6 };

7

8 cout << sizeof refClass << endl; // print 4

所以结论就是引用和指针一样实际占内存空间4个字节。

正确结论:

不要用汇编结果来替代概念,引用不占空间意思就是不占对象空间,不表示不占指针的少量空间。实际上指针是汇编工具实现引用的一种方式而已,而有的优化结果可能没有代表自己的指针。

总而言之,引用就是引用,是这种概念,它为方便程序员使用,和方便汇编工具优化而产生。汇编怎么实现和优化是汇编的事,至于出了什么违反该概念的结果,是汇编的错,而不是定义的错,不要本末倒置。

你可以通过汇编来了解编译器怎样实现引用

引用 却不应该用汇编来解释 它只是一个概念

赞同,引用只是编译器之上,给出来的一个抽象定义。接口的实现,由编译器来决定!

仔细想想,确实如此,引用只是一个概念,为我们提供了一个接口。怎么实现,由编译器自己决定。

原文链接:http://www.cnblogs.com/ronny/p/3662556.html

[C++]全面理解C++中的引用的更多相关文章

  1. 理解Java中的引用传递和值传递

    关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天学习 ...

  2. (转载)理解Java中的引用传递和值传递

      关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天 ...

  3. 从编译器角度理解C++中的引用和指针

    欲分析指针和引用,则要分析变量名和地址之间的关系(不管你理解还是不理解,无论你是从老师那里听到的,还是网上看到的,应该都知道两句话:1. 指针就是地址,2.引用就是给变量起个别名) 所以我们就要来分析 ...

  4. 理解Java中的弱引用(Weak Reference)

    本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出, ...

  5. 深入理解PHP中赋值与引用

    原文:深入理解PHP中赋值与引用 先看下面的问题: <?php $a = 10;//将常量值赋给变量,会为a分配内存空间 $b = $a;//变量赋值给变量,是不是copy了一份副本,b也分配了 ...

  6. 非常易于理解‘类'与'对象’ 间 属性 引用关系,暨《Python 中的引用和类属性的初步理解》读后感

    关键字:名称,名称空间,引用,指针,指针类型的指针(即指向指针的指针) 我读完后的理解总结: 1. 我们知道,python中的变量的赋值操作,变量其实就是一个名称name,赋值就是将name引用到一个 ...

  7. 理解--->Java中的值传递&引用传递

    转自:http://url.cn/5tL9F5D 值传递和引用传递 值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际 ...

  8. 深入理解python中函数传递参数是值传递还是引用传递

    深入理解python中函数传递参数是值传递还是引用传递 目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是"传对象引用 ...

  9. 深入理解 PHP7 中全新的 zval 容器和引用计数机制

    深入理解 PHP7 中全新的 zval 容器和引用计数机制 最近在查阅 PHP7 垃圾回收的资料的时候,网上的一些代码示例在本地环境下运行时出现了不同的结果,使我一度非常迷惑. 仔细一想不难发现问题所 ...

  10. 深刻理解Java中形參与实參,引用与对象的关系

    声明:本博客为原创博客,未经同意.不得转载! 原文链接为http://blog.csdn.net/bettarwang/article/details/30989755 我们都知道.在Java中,除了 ...

随机推荐

  1. SpringBoot 2.5.5整合轻量级的分布式日志标记追踪神器TLog

    TLog能解决什么痛点 随着微服务盛行,很多公司都把系统按照业务边界拆成了很多微服务,在排错查日志的时候.因为业务链路贯穿着很多微服务节点,导致定位某个请求的日志以及上下游业务的日志会变得有些困难. ...

  2. 20220729 - DP训练 #2

    20220729 - DP训练 #2 时间记录 \(8:00-8:10\) 浏览题面 \(8:10-8:50\) T1 看题想到了建树,从每一个点遍历,若能遍历每一个点,则可以获胜 快速写完之后,发现 ...

  3. 齐博x1第四季《模块插件的制作》系列21-公共表单器的参数选项(7)

    password 密码类型 和text一样,只不过type类型是password,密码类型输入时,显示星号.即Html中的密码框类型 icon 字体图标类型 和列表页一样,把css的字体图标引入到了表 ...

  4. 事件循环Event Loop

    在 事件循环 期间的某个时刻,运行时会从最先进入队列的消息开始处理队列中的消息.被处理的消息会被移出队列,并作为输入参数来调用与之关联的函数.正如前面所提到的,调用一个函数总是会为其创造一个新的栈帧. ...

  5. 20_Vue如何监测数组类型数据发生改变的?

    通过上一节,我们知道了vue检测对象数据发生改变的原理 但是还有个api我们没有讲解,Vue.set(): 这个API比较适合在理解了对象检测的原理后进行讲解 案例准备 html <!-- 创建 ...

  6. 有依赖的背包问题(Acwing 10)

    1 # include<iostream> 2 # include<cstring> 3 # include<algorithm> 4 using namespac ...

  7. 用于数据科学的顶级 C/C++ 机器学习库整理

    用于数据科学的顶级 C/C++ 机器学习库整理 介绍和动机--为什么选择 C++ C++ 非常适合 动态负载平衡. 自适应缓存以及开发大型大数据框架 和库.Google 的MapReduce.Mong ...

  8. Blender建模软件怎么安装?有哪些好用的插件?

    1.下载Blender软件包,将压缩包解压后,使用鼠标左键双击打开安装文件. 2.由于压缩包内有两个安装文件,用户需要根据系统版本选择安装.可以使用鼠标右键单击桌面上的"此电脑"图 ...

  9. 给ofo共享单车撸一个微信小程序

    想学一下微信小程序,发现文档这东西,干看真没啥意思.所以打算自己先动手撸一个.摩拜单车有自己的小程序,基本功能都有,方便又小巧,甚是喜爱.于是我就萌生了一个给ofo共享单车撸一个小程序(不知道为啥of ...

  10. ADPCM(自适应差分脉冲编码调制)的原理和计算

    关于ADPCM ADPCM(Adaptive Differential Pulse Code Modulation, 自适应差分脉冲编码调制) 是一种音频信号数字化编码技术, 音频压缩标准G.722, ...