[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中,除了 ...
随机推荐
- 齐博x1钩子自动添加频道参数变量
频道或插件,增加功能的时候,可能要在后台增加开关参数.这个时候只需要增强对应的接口文件即可,比如创建这样一个文件\application\shop\ext\setting_get\give_jifen ...
- 齐博x1如何开启自定义标签模板功能
为安全起见,同时也为了避免用户随意添加风格导致默认模板不协调,系统默认关闭了类似V系列的自定义修改模板功能.如下图所示,默认是关闭的 你如果需要启用的话,把下面的代码,参考下图导进去后,就可以增加一个 ...
- Centos镜像下载
1.进入官网,并点击下图所示的红框(alternative downloads) 官网网址:https://www.centos.org/download/ 2.在往下翻,可以看到如下图的历史版本, ...
- 一键生成CA证书
create_cert () { cd /etc/openvpn/easy-rsa ./easyrsa gen-req ${NAME} nopass <<EOF EOF cd /etc/o ...
- mitmproxy抓包工具
中文官网 https://ptorch.com/docs/10/mitmproxy-concepts-options mitmproxy抓包工具 1. mitmproxy 介绍与安装 需要安装pyth ...
- Jupyter基本使用
https://www.cnblogs.com/zhrb/p/12174167.html 用来取代Jupyter Notebook的一个基于Web的用户交互式用户界面.相当于增强版的Jupyter N ...
- springboot使用jira-rest-java-client-api集成jira,自定义对查询board和sprint的支持
公司内部使用jira作项目管理,我接到新的需求,要在测试报告上获取jira的所有项目,再根据项目获取board看板,再根据看板获取Sprint,最后获取未完成的bug信息.效果如下: 第一次接入jir ...
- Revit2021保姆级安装教程
Revit2021 WIN10 64位安装步骤: 1.先使用"百度网盘客户端"下载Revit_2021软件安装包到电脑磁盘里,并解压缩,安装全程需连接网络,然后双击打开Revit_ ...
- mindxdl--common--http_handler.go
// Copyright (c) 2021. Huawei Technologies Co., Ltd. All rights reserved.// Package common this file ...
- i春秋Fuzz
点开只有三个单词plz fuzz parameter 大概意思就是让我们疯狂尝试参数... 我们通过url尝试传入参数 ?user=123 ?name=123 ?username=123 ?id=12 ...