昨天看了C++primer的2.5节,引用变量,看完之后,对引用变量有一些疑惑,然后自己编写了一些测试程序,想搞清楚引用变量的底层到底做了些什么。下面,首先,我从C++ primer一书的2.5节内容讲起,再进行一些延伸。
一、C++ primer2.5节学习笔记:
注意(1)将普通的引用绑定到const对象是不合法的
(2)const引用可以初始化为不同类型的对象或者初始化为右值,而非const引用引用不能初始化为右值
原因在于:一般情况下,应该使非const引用指向同类型的变量,当指向的是不同类型的变量时,会出现以下情况:
例:
编写如下的测试程序:
1 int main()
2
3 {
4
5 double dval = 3.14;
6
7 const int &ri = dval;
8
9 return 0;
10
11 }

如上图所示,能够编译成功,但是会显示这么一条warning,将double类型转化为const int类型,可能会loss of data。
首先,看dval存储的地址,是0x0012ff5c,而ri存储的地址是0x0012ff44,而引用所在的地址应该和其指向的变量的地址相同,出现这个问题就是因为引用和其指向的变量类型不同,实际上,编译器会将const int &ri = dval; 转化为如下代码:
1 int temp = dval;
2
3 const int &ri = temp;
所以ri引用指向的已经不是dval,而是这个过程中创建的一个临时变量temp,进行强制类型转化(转化为了3),这一点从内存中也能够看出,ri指向的变量地址是0x0012ff44,此处,存储着0x0000_0003,即一个整型变量(这里占了4个字节)

所以,如果ri不是const,那么就可以对ri进行改变,但是程序员的原意是通过ri的修改从而改变dval,但是这种情况下,改变ri的值不能改变dval,而只能改变temp(实际上没有这个变量名)的值,这和程序员的原意是违背的,所以C++标准中干脆将这种用法规定为不合法,下面是我们将const int&ri = dval;改成了int &ri = dval;后编译出现的情况:

从上图中可以看出,这种用法是不合法的。
二、探究C++的引用:
从一条语句的反汇编看起:
1 const int &ri = dval; // const引用指向不同的类型

首先,dval的地址是在0x0012ff5c,这里的8字节数据存储着浮点数3.14,然后,观察const int &ri = dval;反汇编后的代码:
004113E7 fld qword ptr [dval] ;浮点协处理器指令
004113EA call @ILT+200(__ftol2_sse) (4110CDh) ; 应该调用函数是进行强制类型转化,结果存在eax寄存器中
004113EF mov dword ptr [ebp-24h],eax ; 将强制类型转化后的结果存在内存中
; (可理解为C++ primer书中所说的产生了一个temp变量
; 这个变量的地址就是ebp-24h)
004113F2 lea eax,[ebp-24h] ; 将所存结果的地址(lea)给eax寄存器
004113F5 mov dword ptr [ri],eax ;即将上面所说的地址(不妨认为是temp变量的地址)
;给变量ri所在的内存区域
单步完这一步后,可以看到内存中有两处颜色变红了,表示在这一过程中内存中有两处进行了修改,根据上面的反汇编的分析,的确应该是这样,首先,是内存中的[ebp-24h]处(”temp变量“处)变成了0x0000_0003,然后是将这个临时变量的地址,即ebp-24h的值赋给了ri变量所在的内存区域。
实际上,将鼠标指针放在ebp的上面可以看到其十进制的值,如下所示

所以此时ebp = 1245032 = 0x0012ff68
ebp-24h = 0x0012ff44
所以ri变量处的内容应该是0x0012ff44
而ri变量的地址应该是0x0012ff50,从上面的内存图中可以看出,0x0012ff50在这一过程中变化了,并且内容是0x0012ff44,实际上,在反汇编的窗口中进行单步也能够看出这一点:
”temp变量“改变前:

”temp变量“改变后,ri变量改变前:

ri变量改变后:

从上面的三幅图中可以看出,我们前面的结论是正确的。
但是,在这里出现了一个问题,我们来查看Watch窗口,可以看到:

??????????????????????????
上面和我们前面按照反汇编理解的是不一样的,按照我们的理解,应该是:
ri:存储着被引用变量所在的地址(这里应该是”temp变量“的地址),即0x0012ff5c,
*ri:即0x0012ff5c存储单元的内容,即3
&ri: 即ri变量所在的地址,按照前面调试的结果,应该是0x0012ff50。
但是在Watch窗口中看到的结果是:
ri = 3
*ri = 3
&ri是temp变量的地址,0x0012ff5c
上面的这个问题先放着,先来看这个测试程序的最后两句,是两个普通的非引用变量的初始化,我们来看这部分的反汇编代码:

可以看到val1变量的初始化很简单,就是把一个常量5放到了内存中变量val1所在处,而val2的初始化也可理解为就是把变量val1的内容搬移到val2所在处,但是考虑到x86系列不能再两个存储器单元之间传送数据,所以这里需要通过一个寄存器eax,把val1中的内容搬到了val2中,注意到,这里用的汇编指令都是mov,从这里,联系前面的分析,就能看出引用变量和非引用变量进行初始化时的最重要区别,非引用变量初始化时,给这个变量开辟了一个内存空间,将等号右边的表达式结果搬移(mov)到了这个变量所在的内存单元处,而引用变量初始化时,也给这个变量开辟了内存空间,但是区别在于是将等号右边的相同类型变量(被引用变量)的地址赋给了这个引用变量(这是就非const变量或者const变量时的一种特殊情况讨论的,若是const变量,并且等号右边和引用变量不是同一类型时,则在中间要加上一步,就是创建一个临时变量(类型和引用变量相同),给这个临时变量开辟一段内存空间,然后把等号右边的表达式的结果存入这个临时变量中,接下来,再把这个临时变量的地址赋给这个引用变量)。
所以,从这一点上来看,引用完成的功能和指针很像,但是可能是C++必须要区分开引用这种用法,所以虽然从上面来看引用变量的类型实际上是一个指针类型,但是编译器可能不这样看,而是把在代码中出现的这个引用类型都翻译成引用类型的内容,所以在后面的程序中使用引用类型的时候,比如说改变引用ri的值的时候,实际上改变的是*ri(这里先把ri看成指针类型理解吧)的值,这可能也是前面在Watch窗口中查看时出现问题的原因。
上面这段话只是我的推测,在C++方面看的书还比较少
下面接着来看下面的语句的反汇编结果和执行情况:
1 const double &ri2 = dval; // const引用指向相同的类型
执行后:

可以看出,const变量指向相同类型的变量时,实际上在内存中也开辟了一个空间,这个空间中放置着ri2变量,ri2变量的内容是一个地址,即被引用变量dval的地址,但是,如前所述,按照这种指针的方法理解的话,就会把引用变量ri2理解成了指针(其实从这个结果来看,ri2就是一个指针变量),所以,以后理解引用变量时,最好就把引用变量看成一个不占用任何内存空间的”变量“,其地址就是被引用变量的地址(仅仅就两者类型一致时,不一致时有”临时变量“),就好像引用变量是被引用变量的一个别名。
接着看下面的语句:
1 double &ri3 = dval; // 非const引用指向相同的类型
这和上面的const引用指向相同的类型实际上是一样的。
结果截图如下:

最后,看const引用指向右值(常数)的情况:
1 const int &ri4 = 4; // const引用指向右值(常数)
结果为:

实际上,这和const变量指向不同类型的情况是相同的。也存在着一个临时变量。
下面,对前面的分析进行一个总结:
一方面,按照反汇编分析的结果来看:
(1)如果引用变量指向的是相同类型的变量,那么,首先会为这个引用变量开辟一段内存空间(实际上这是在编译阶段就完成了),然后把被引用变量的地址放到这段内存空间中,即把被引用变量的地址赋给引用变量,
(2)如果引用变量初始化式右端的变量和引用变量是不同类型的,或者初始化式右边就是一个右式(这两种情况都只有const引用是合法的),这时,首先,将开辟一段内存空间,把初始化式右边的结果(可能需要强制类型转化等)放在这个内存空间中(可理解为定义了一个临时变量),然后再把这个临时变量对应的地址赋给引用变量。
但是前面已经讲了,按照反汇编的结果理解的话,可能会有将引用和指针混淆的疑惑,而实际上,在C++中使用引用时,也不需要知道这么多的底层细节,所以,可以这样来理解引用:
(1)如果引用变量指向的是相同类型的变量,那么这个引用变量就相当于这个被引用变量的一个别名,两者占用相同的内存空间,对引用变量做的任何改变也会改变相应的被引用变量。
(2)如果引用变量初始化式右端的变量和引用变量是不同类型的,或者初始化式右边就是一个右式(这两种情况都只有const引用是合法的),这时,首先,编译器会定义一个临时变量,把初始化式右边的结果(可能需要强制类型转化等)赋给这个临时变量,然后再将原引用指向这个类型相同的临时变量(这一步的过程和(1)相同)。
下面是写的这段测试程序的反汇编代码,放在这里方便查看:

最后,说明一下,这篇文章中很多都是我自己的看法,有些没在书上找到,不一定正确,如果有错,希望大家提醒!
分享到:
相关推荐
在C++中,引用相当于给已存在的变量起了一个别名。它通过 `&` 符号与变量类型连用声明,如 `int &re=a;` 这样的声明让变量 `re` 成为变量 `a` 的引用。一旦声明,引用便与被引用的变量紧密绑定,不可更改。引用的...
在C++编程中,理解对象模型至关重要,因为它揭示了程序在内存中的布局方式。本文将基于对String类的重写,探讨C++的内存模型,包括不同类型的变量和函数的存储区域,以及对象的创建和生命周期。 首先,我们关注的是...
8. **C++11及以后的新特性**:虽然《Think in C++》是英文原版,但很可能包含了对C++11及后续版本新特性的介绍,如lambda表达式、右值引用、自动类型推导等。 9. **HTML格式**:此版本以HTML格式呈现,意味着可以...
在C++的学习中,首先会接触到基础语法,包括变量、数据类型(如int、char、float等)、运算符(算术、比较、逻辑等)、流程控制(如if条件语句、for循环、while循环)以及函数的使用。这些是编程的基础,理解和掌握...
通过郭炜老师的《新标准C++习题解答》,学习者可以深入探究这些新特性,并通过习题实践来巩固理解。每一章的习题都覆盖了相应的主题,从基本语法到高级应用,逐步引导学习者成为熟练的C++程序员。解答部分详细解释...
1. **C++基础**:C++是面向对象的编程语言,它的基础包括变量、数据类型、运算符、流程控制(如if语句和循环)、函数等。源代码中可能包含这些基本元素的示例,通过阅读和运行这些代码,你可以更好地掌握C++的基础...
首先,C++的基础部分包括语法、变量、数据类型、运算符、流程控制语句等。掌握这些基础知识是学习C++的起点。例如,理解基本的数据类型(如int、float、char)和复合数据类型(如数组和结构体),以及如何通过运算符...
以上只是C++11标准中部分重要特性的简介,实际的学习过程中还需要深入探究每个特性的细节,以及如何在实际项目中有效应用这些新功能。通过阅读《C++11深度剖析》这本书,读者可以全面理解并掌握这些现代C++编程技巧...
《深入理解C++11:C++11新特性解析与应用》这本书是C++开发者的重要参考资料,它全面深入地探讨了C++11标准引入的新特性及其在实际编程中的应用。C++11是C++语言的一个重大更新,极大地提高了代码的效率、安全性和...
在IT行业中,C++是一种强大的、面向对象的编程语言,被广泛应用于系统软件、游戏开发、金融服务和嵌入式系统等多个领域。对于求职者来说,掌握C++技能并熟悉其面试题是至关重要的。"C++面试题大全"这个资源正是为了...
C++11及其后续版本引入了很多新的特性,如lambda表达式、右值引用、自动类型推断(auto关键字)等,这些都极大地提升了C++的现代性和易用性。 学习C++时,不仅要理解语法,还要探究其背后的设计哲学和运行机制。...
理解指针的概念、声明、初始化、解引用、指针运算以及指针作为函数参数和返回值是学习C++必不可少的部分。 3. **类与对象**:面向对象编程(OOP)是C++的核心特性。类是对象的蓝图,包含了数据成员(属性)和成员...
函数重载允许同一函数名根据参数列表的不同有多种不同的实现,而引用则提供了一种安全且高效的方式去传递和修改变量的值。 基于对象的设计是C++的核心,引入了类的概念,类本质上是抽象数据类型,用于封装数据和...
C++的基础概念如变量、数据类型、运算符、控制结构等构成了编程的基石。学习者通过课件可以掌握如何使用这些基础元素来构建程序。随着学习的深入,课件将介绍面向对象编程(OOP)的核心思想,包括类与对象、继承、多...
4. **指针与引用**:分析指针和引用在内存中的表示,以及它们如何作为对象的间接访问手段,特别是在多态性中的应用。 5. **继承与多态**:探究派生类如何继承基类的属性,虚函数机制如何实现多态,以及动态绑定...
对于C++ Primer第四版的习题,特别关注C++11及以后标准引入的新特性,如智能指针、lambda表达式、右值引用和类型推断等。这些都是现代C++编程中不可或缺的工具,掌握它们能让你的代码更加简洁、安全和高效。 总之,...
C++中的参数传递有值传递、引用传递和指针传递三种方式。值传递会复制实参的副本,不改变原值;引用传递不复制,而是让函数直接作用于原变量;指针传递则通过指针操作原变量。理解这三种方式的差异和应用场景,能...
**C++基础试题与答案详解** C++是一种广泛使用的编程语言,尤其...对于每个问题,建议不仅要了解答案,还要深入探究其背后的原理,这样才能真正掌握C++语言。不断练习和实践,是成为一名熟练的C++程序员的关键步骤。
4. **深入研究**: 探究C++标准库的内部机制,阅读经典书籍如《C++ Primer》和《Effective C++》等。 总之,郑莉教授的C++课程讲义PPT涵盖了C++语言的各个方面,是学习C++的理想资料。通过系统学习,不仅可以掌握...
1. **异常处理**:iOS中的异常处理机制主要由Objective-C的`@try/@catch/@finally`和C++的`try/catch`构成。异常是程序运行时出现的错误,通常与逻辑错误有关。当异常未被捕获时,会导致程序崩溃。 2. **内存管理**...