[C++]全面理解C++中的引用
一、引用的本质是什么
说到引用,一般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++中的引用的更多相关文章
- 理解Java中的引用传递和值传递
关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天学习 ...
- (转载)理解Java中的引用传递和值传递
关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天 ...
- 从编译器角度理解C++中的引用和指针
欲分析指针和引用,则要分析变量名和地址之间的关系(不管你理解还是不理解,无论你是从老师那里听到的,还是网上看到的,应该都知道两句话:1. 指针就是地址,2.引用就是给变量起个别名) 所以我们就要来分析 ...
- 理解Java中的弱引用(Weak Reference)
本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出, ...
- 深入理解PHP中赋值与引用
原文:深入理解PHP中赋值与引用 先看下面的问题: <?php $a = 10;//将常量值赋给变量,会为a分配内存空间 $b = $a;//变量赋值给变量,是不是copy了一份副本,b也分配了 ...
- 非常易于理解‘类'与'对象’ 间 属性 引用关系,暨《Python 中的引用和类属性的初步理解》读后感
关键字:名称,名称空间,引用,指针,指针类型的指针(即指向指针的指针) 我读完后的理解总结: 1. 我们知道,python中的变量的赋值操作,变量其实就是一个名称name,赋值就是将name引用到一个 ...
- 理解--->Java中的值传递&引用传递
转自:http://url.cn/5tL9F5D 值传递和引用传递 值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际 ...
- 深入理解python中函数传递参数是值传递还是引用传递
深入理解python中函数传递参数是值传递还是引用传递 目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是"传对象引用 ...
- 深入理解 PHP7 中全新的 zval 容器和引用计数机制
深入理解 PHP7 中全新的 zval 容器和引用计数机制 最近在查阅 PHP7 垃圾回收的资料的时候,网上的一些代码示例在本地环境下运行时出现了不同的结果,使我一度非常迷惑. 仔细一想不难发现问题所 ...
- 深刻理解Java中形參与实參,引用与对象的关系
声明:本博客为原创博客,未经同意.不得转载! 原文链接为http://blog.csdn.net/bettarwang/article/details/30989755 我们都知道.在Java中,除了 ...
随机推荐
- 14.MongoDB系列之配置分片
1. 启动服务器 1.1 启动配置服务器 配置服务器是集群的大脑,保存着关于每个服务器包含哪些数据的所有元数据,因此,必须首先创建配置服务器. 由于资源限制,在同一机器上启动三个进程 # mkdir ...
- 这才是使用ps命令的正确姿势
这才是使用ps命令的正确姿势 前言 在linux系统当中我们通常会使用命令去查看一些系统的进程信息,我们最常使用的就是 ps (process status).ps 命令主要是用于查看当前正在运行的程 ...
- XPAND模板语言语法1.0
XPAND模板语言语法1.0 Xpand模板语言一般写在以.xpt为结尾的文本文件中 ,以"« »" 作为开头和结尾 .Xpand语言主要包括以下几个标签: «IMPORT», ...
- .NET Core C#系列之XiaoFeng.Data.IQueryableX ORM框架
当前对象操作数据库写法和EF Core极度类似,因为现在大部分程序员都懒得去写SQL,再一个就是项目作大了或其它原因要改数据库,每次改数据库,那么写的SQL语句大部分要作调整,相当麻烦,并且写SQ ...
- day09-Tomcat01
Tomcat01 1.WEB开发介绍 WEB,在英文中WEB表示网/网络资源,它用于表示WEB服务器(主机)供浏览器访问的资源 WEB服务器(主机)上供外界访问的Web资源为: 静态web资源(如ht ...
- vulnhub靶场之DOUBLETROUBLE: 1
准备: 攻击机:虚拟机kali.本机win10. 靶机:DOUBLETROUBLE: 1,网段地址我这里设置的桥接,所以与本机电脑在同一网段,下载地址:https://download.vulnhub ...
- 【笔记】CF1714F Build a Tree and That Is It 及相关
题目传送门 细节较多的构造题. 解决思路 题目中虽然说是无根树,但我们可以钦定这棵树的根为 1,方便构造,这是不影响结果的. 以下记给定的三段长度为 \(a,b,c\) . 先考虑无解的情况. 首先, ...
- vue3路由的使用
一.路由的概要 1.1.什么是路由? 路由就是一组映射关系,根据不同的 url 地址展示不同的内容或页面(key-value): key为路径,value可能是function或component 路 ...
- GO语言内存操作指导—unsafe的使用
在unsafe包里面,官方的说明是:A uintptr is an integer, not a reference.Converting a Pointer to a uintptr creates ...
- elasticsearch 之 histogram 直方图聚合
目录 1. 简介 2. bucket_key如何计算 3. 有一组数据,如何确定是落入到那个桶中 4.需求 4.1 准备mapping 4.2 准备数据 5.histogram聚合操作 5.1.根据r ...