本文从编译器角度,通过反汇编手段,深入的理解了1.引用的理解与使用2.结构体和类做参数时的底层实现,并对函数参数和返回值的各种情况做了归纳。希望能对大家有用,也希望大家能针对本文中的一些不足和缪误给予指正。
本文中例子均是用VS系列的VS2008编译环境下测试的。
第一章 概念介绍与理解
首先从大的方面来分,参数和返回值的传递可以分为pass by value、pass by point、pass by reference三类。
值,就是某种类型的变量在内存中的表现形式。//这么说貌似有点牵强
本文将为了方便讨论,把值分为3类:
1. 简单型值 非浮点型值的其他原生C++类型,以及任何类型的指针、引用。
2. 浮点型值 浮点型值值传递略有不同,具体的讲解,我们放到第四章。
3. 复杂型值 这里指的复杂型值,包括数组和自定义的类型(结构体和类)。
对复杂型值传递的讲解,我们放到第五章。
在C++中任何数据类型都有自己对应的指针类型,指针也是一种数据类型。指针的值是各种类型变量在内存中的地址。
引用是一种特殊的指针。引用表示一个变量的别名(此别名其实是变量的地址),对它的任何操作,本质上都是在操作它表示的变量。如取地址&,累加++,sizeof等。
其实指针和引用也是值的一种,而且可以归为本文定义的简单型值一类。但是本为为了解释传值传址传引用的区别,把这两个单独拿出来讨论。
引用
引用的规则
(1)引用被创建的同时必须被初始化。
(2)不能有NULL引用,引用必须与合法的存储单元关联。
(3)一旦引用被初始化,就不能改变引用的关系。
引用的本质
引用类型在C++中被描述为变量的别名。实际上,C++为了简化指针操作,对指针的操作进行了封装,产生了引用类型。
实际上引用类型就是指针类型,只不过它用于存放地址的内存空间对使用者而言是隐藏的。
引用类型的存储方式和指针是一样的,都是使用内存空间存放地址值。在C++的内存体现来看,引用和指针没有分别。只是引用是通过编译器实现寻址,而指针需要手动寻址。
引用类型的参数也占内存空间,其中保存的数据是所引用数据的地址值。在反汇编下,没有引用这种数据类型。
---钱林松 《C++反汇编与逆向分析技术揭秘》
综上,引用在本质实现上,与指针是相同的,引用其实可以理解为是一种 TYPE* const的常指针(这样我们就可以理解为什么会规定引用的这样三条规则,当然const的约束也是编译器完成的)。
而在使用上,除了要满足引用的规则外,其他操作的操作方法和对数据的影响都与操作它所表示的变量一致。
引用其实可以看作是一种语法糖。
引用和指针的区别及其原因
1.引用必须被初始化,指针可以不被初始化,后期再赋值亦可。
引用为常指针的事实,造成若不初始化,后期访问可能会出现内存访问异常。
2.不能有 NULL引用,但可以有NULL指针。
引用为常指针的事实,造成若寸NULL引用,后期访问肯定会出现内存访问异常。
3.引用被初始化后,不可以被修改,指针可以随时更改。
引用为常指针的事实,造成不可以被修改的特性。
4.对引用的任何操作,都会被重定向到它所引用的变量,而指针则需要operator*修饰才可。
此过程由编译器控制,编译器会把对引用初始化之后的一切操作,解释为寻址并操作其引用的变量的操作。
5.引用做函数返回值时候可以被用作左值,指针不可以。
因为做返回值时,会是被寄存器eax返回,也就是说eax中的值是被引用或被指向变量的地址。当返回值为引用时候,对其的任何操作会被重新解释,到对其所引用的变量上。而
当返回值为指针时,对其的操作被认为是对该指针变量的操作,该指针变量在寄存器中,无法操作寄存器中值。
6.引用是类型安全的,指针不是。
这条有待理解。
PS:有些人认为引用不能用const修饰,这是错误的观点。
int n = 10;
const int& nRef1 = n; //表示其引用的变量为常量
int const& nRef2 = n; //表示其引用的变量为常量
int& const nRef3 = n; //warning C4227: 使用了记时错误: 忽略引用上的限定符
//表示该引用为常量,由于引用本身就不可修改,所以被忽略。
//这种表示方法与int& nRef3 = n;完全相同。
以上三种位置的const修饰符均被编译器认可。
引用的初始化和访问
测试代码及反汇编:
int n = 10;
0041143E mov dword ptr [n],0Ah
int& nRef = n; //初始化引用
00411445 lea eax,[n] //取变量n的地址放入eax
00411448 mov dword ptr [nRef],eax //将eax中n的地址放入nRef所指示的内存单元,
//由此可见,引用其实是需要占用内存空间的,其空间内存储的是所表示变量的地址值
nRef = 20;
0041144B mov eax,dword ptr [nRef] //将nRef中的值(地址值放入到eax
0041144E mov dword ptr [eax],14h //对eax所指空间的内容进行赋值
//此处的操作跟指针的操作相同