一、引用的本质是什么

说到引用,一般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. 不妨试试更快更小更灵活Java开发框架Solon

    @ 目录 概述 定义 性能 架构 实战 Solon Web示例 Solon Mybatis-Plus示例 Solon WebSocket示例 Solon Remoting RPC示例 Solon Cl ...

  2. 42.JSON Web Token认证

    JSON Web Token认证介绍 简称JWT认证,一般用于用户认证 JWT是一种相当新的标准,可用于基于token的身份验证 与内置的TokenAuthentication方案不同,JWT不需要使 ...

  3. 1.-Django项目结构

    一.Django简介 Django是一个开放源代码的Web应用框架,由Python写成.采用了MTV的框架模式,即模型M,视图V和模版T.   Django基本组件: 1.基本配置文件/路由系统 2. ...

  4. 十一、Pod的健康检查-探针

    Pod 的健康检查-探针 一.Pod 的健康检查-探针 1.1.探针基本概念 ​探针是由 kubelet 对容器执行的定期诊断.要执行诊断,kubelet 调用由容器实现的 Handler 有三种类型 ...

  5. centos7离线安装PHP7

    环境 centos7.9 PHP7.4.30 准备工作 在编译PHP时会提示一些包版本不够或者缺少某些包,一般选择yum来安装缺少的包,但因为是离线安装,所以可以手动配置本地yum源.先看一下系统版本 ...

  6. python 的time、datetime模块

    python 时间模块 import datetime ​ res = datetime.datetime.now() print(res) # 2022-08-07 16:47:07.120459 ...

  7. C#中winform DataGridView常用修改点

    1.修改列名 一般情况下,从数据库里面读取的列名是英文或者拼音,但是,有时候显示需要中文,这样就需要修改列名了. dgv.Columns[0].HeaderCell.Value="编号&qu ...

  8. networkQuality

    基本使用 networkQuality 是一个命令行工具,需要使用「终端」App(或者你首选的其他终端模拟器)运行.方法是: 首先,点按「程序坞」(Dock)中的「启动台」(LaunchPad)图标, ...

  9. [leetcode] 994. Rotting Oranges

    题目 You are given an m x n grid where each cell can have one of three values: 0 representing an empty ...

  10. springboot中使用mybatis_plus逆向工程

    创建springboot项目,选择图片中所示依赖 mybatis-plus生成的依赖 <!-- mybatis_plus --> <dependency> <groupI ...