C++的那些事:你真的了解引用吗
一、引用的本质是什么
说到引用,一般C++的教材中都是这么定义的:
1,引用就是一个对象的别名。
2,引用不是值不占内存空间。
3,引用必须在定义时赋值,将变量与引用绑定。
那你有没有想过,上面的定义正确吗?编译器是如何解释引用的?
这里先给出引用的本质定义,后面我们再进一步论证。
1,引用实际是通过指针实现的。
2,引用是一个常量指针。
3,引用在内存中占4个字节。
4,在对引用定义时,需要对这个常量指针初始化。
二、探究本质
我们从最简单的变量的定义开始,看编译器会做哪些事情。
int var = ;
mov dword ptr [var],2Ah // 对应汇编代码
上面语句申请了一块内存空间,占4个字节,存放了一个int型的变量。内存里放的是42的二进制码。
汇编代码向我们表达的意思就是把42写入以var为地址的内容区域。var有点像我们理解上的指针,只是编译器并没有把它抽象出来,而是让我们更表象的理解:申请一个变量,它的值为42。
那么var这个变量名放在哪呢?
我们知道程序如果访问内存里的数据,需要通过地址来进行访问,所以上面的代码在经过编译器生成目标代码时,用存放42的地址了所有的var,所以结论时,目标文件中不存在var,所以变量名本身是不占内存的。
而我们知道,引用是变量的一个别名。那么,从这很多人会联想到,引用会不会也只是一个名字而已,编译器在生成目标代码的时候,会用实际地址替换引用呢?
答案并非这样!
那我们接下来看看,当我们定义一个引用时,发生了什么:
int var = ;
01303AC8 mov dword ptr [var],2Ah
int& refVar = var;
01303ACF lea eax,[var]
01303AD2 mov dword ptr [refVar],eax
上面的代码显示,当定义一个引用时,编译器将var的地址赋给了以refVar为地址的一块内存区域。也就是说refVar其实存放的是var的地址。
这让我们联想到了指针,那么我们看看定义一个指针是发生了什么:
int var = ;
01213AC8 mov dword ptr [var],2Ah
int* ptrVar = &var;
01213ACF lea eax,[var]
01213AD2 mov dword ptr [ptrVar],eax
没错,没有任何差别,定义一个引用和一个指针的汇编代码完全一致!
三、const哪里去了
相信从上面的分析时,你可能已经相信了,引用实际上就是一个指针。那么为什么说引用是一个常量指针呢,在目标代码里有什么体现呢?
这个问题其实要从C++底层机制谈起,C++为我们提供的各种存取控制仅仅是在编译阶段给我们的限制,也就是说编译器确保了你在完成任务之前的正确行为,如果你的行为不正确,那么编译器就是给你在编译时提示错误。所谓的const和private等在实际的目标代码里根本不存在,所以在程序运行期间只要你愿意,你可以通过内存工具修改它的任何一个变量的值。
这也就解释了为什么上面的两段代码中引用和指针的汇编代码完全一致。
C++设计引用,并用常量指针来从编译器的角度实现它,目标是为了提供比指针更高的安全性,因为常量指针一旦与变量地址绑定将不能更改,这样降低了指针的危险系数,它提供了一种一对一的指针。
但是你觉得使用引用就安全了吗?它同样会有与使用指针一样的问题
int *var = new int();
int &ref = *var;
delete var;
ref = ;
return ;
上面这段代码就很不安全,因为ref引用的内存区域不合法。
为了进一步验证引用与指针在本质上的相同,我们看当引用作为函数参数传递时,编译器的行为:
void Swap(int& v1, int& v2);
void Swap(int* v1, int* v2); int var1 = ;
00A64AF8 mov dword ptr [var1],
int var2 = ;
00A64AFF mov dword ptr [var2],
Swap(var1,var2);
00A64B06 lea eax,[var2]
00A64B09 push eax
00A64B0A lea ecx,[var1]
00A64B0D push ecx
00A64B0E call Swap (0A6141Fh)
00A64B13 add esp,
Swap(&var1, &var2);
00A64B16 lea eax,[var2]
00A64B19 push eax
00A64B1A lea ecx,[var1]
00A64B1D push ecx
00A64B1E call Swap (0A61424h)
00A64B23 add esp,
上面代码再次证明了,引用与指针的行为完全一致,只是编译器在编译时对引用作了更严格的限制。
四、引用占多大的内存空间
因为在在表达式中,使用引用实际上就像使用变量本身一样,所以直接用sizeof是得不到引用本身的大小的。
double var = 42.0;
double& ref = var; cout << sizeof var << endl; // print 8
cout << sizeof ref << endl; // print 8
我们可以通过定义一个只含有引用的类来解决这个问题:
class refClass{
private:
double& ref;
public:
refClass(double var = 42.0) :ref(var){}
};
cout << sizeof refClass << endl; // print 4
所以结论就是引用和指针一样实际占内存空间4个字节。
参考文章:http://www.cnblogs.com/rollenholt/articles/1907408.html
C++的那些事:你真的了解引用吗的更多相关文章
- [转载] C++的那些事:你真的了解引用吗
我转载了roony的一篇文章 C++的那些事:你真的了解引用吗 以备以后可以查到. 一.引用的本质是什么 说到引用,一般C++的教材中都是这么定义的: 1.引用就是一个对象的别名. 2.引用不是值不占 ...
- 那些容易忽略的事4-(正则表达式反向引用\n)
n 是一个正整数.一个反向引用(back reference),指向正则表达式中第 n 个括号(从左开始数)中匹配的子字符串. ps1:从左开始数,从左到优,也是从外到里,依次是\1,\2,\3... ...
- 新iPhone要推出双卡双待这事是真的吗?
自2007年发布以来,iPhone似乎一直都是"异类"--以自己独特的方式走着一条引领智能手机前进的路!如,在当年遍地按键键盘的年代,iPhone以触摸屏的奇葩姿态引领了新潮流:刚 ...
- 你确定你真的懂Nginx与PHP的交互?
Nginx是俄国人最早开发的Webserver,现在已经风靡全球,相信大家并不陌生.PHP也通过二十多年的发展来到了7系列版本,更加关注性能.这对搭档在最近这些年,叱咤风云,基本上LNMP成了当下的标 ...
- 不加班的实践(1)——这真的该用try-catch吗?
前言 我有个技能,就是把“我”说的听起来特别像“老子”. 以前是小喽啰的时候,会跟领导说“我!不加班.”,听起来就像“老子不加班!”一样.到最后发现,我确实没有把计划内的工作拖到需要加班才能完成,这个 ...
- java 参数传递是引用传递还是值传递?
JAVA的参数传递倒底是值传递还是引用传递,我觉得两种说法都没错,关键是看怎么理解“引用”和“引用传递”. (一)从编译原理的角度讲,所有那些无法通过形参来修改实参本身的传递机制都是“值传递”,在JA ...
- C++中引用和指针详解
先来分析指针这个东东: 从概念上讲,指针本质上就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变. 上面的图表示了程序运行时变量的值 ...
- 堆栈详解 + 彻底理解Java的值传递和引用传递
本文旨在用最通俗的语言讲述最枯燥的基本知识 学过Java基础的人都知道:值传递和引用传递是初次接触Java时的一个难点,有时候记得了语法却记不得怎么实际运用,有时候会的了运用却解释不出原理,而且坊间讨 ...
- 这真的该用try-catch吗?
前言 我有个技能,就是把“我”说的听起来特别像“老子”. 以前是小喽啰的时候,会跟领导说“我!不加班.”,听起来就像“老子不加班!”一样.到最后发现,我确实没有把计划内的工作拖到需要加班才能完成,这个 ...
随机推荐
- 使用qsort对结构体的数据排序
1007 DNA 排序 题目大意: 序列“未排序程度”的一个计算方式是元素乱序的元素对个数.例如:在单词序列“DAABEC'”中,因为D大于右边四个单词,E大于C,所以计算结果为5.这种计算方法称为序 ...
- 模板插件aTpl.js新增功能
摘要: aTpl.js是一款模板插件,该插件支持ie5+,chrome等浏览器以及移动端浏览器,支持for和if语法,以及表达式.最近对aTpl.js模板插件增加了新的功能,支持字符串模板,同时增加了 ...
- Java 参数的一些心得
java 对象入参是传入的是引用(一块内存), 基础类型是值(复制内容),测试代码如下 public class TestA { private String name; public String ...
- AspectJ报错:error at ::0 can't find referenced pointcut XXX
今天在使用AspectJ进行注解切面时,遇到了一个错误. 切点表达式就是无法识别——详细报错信息如下: Exception in thread "main" org.springf ...
- php增加对mysqli的支持
php增加对mysqli的支持 我在fedora下使用yum安装的php和mysql,但是发现php不支持myslqi,只能编译一个mysqli的扩展给php用了. 方法如下: 1.下载php 2 ...
- 42.旋转数组的最小元素[Get min value of rotated array]
[题目] 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个排好序的数组的一个旋转,输出旋转数组的最小元素.例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5 ...
- 29.调整数组顺序使奇数位于偶数前面[ReOrderArray]
[题目] 输入一个整数数组,调整数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分.要求时间复杂度为O(n). [分析] 如果不考虑时间复杂度,最简单的思路应该是从头扫描这个 ...
- ios 动态测定字符串frame : boundingRectWithSize函数
用NSString中的一个方法,第一个参数是text形成的frame的最大的宽度和高度, 其中的options比较关键 CGSize labelSize = [text boundingRectWit ...
- Java for LeetCode 045 Jump Game II
Given an array of non-negative integers, you are initially positioned at the first index of the arra ...
- DP:Bridging Signals(POJ 1631)
不能交叉的引脚 (这一题的难度在于读题)题目大意:有一堆引脚(signals),左边一排,右边一排,左边从上到下,对应着连接右边的引脚(所有的引脚都被接上),现在引脚之间的连线有交叉,我们要桥接这些交 ...