C++的那些事:你真的了解引用吗

1、引用的本质是什么

说到引用,通常C++的教材中都是这么定义的:html

1,引用就是一个对象的别名。安全

2,引用不是值不占内存空间。函数

3,引用必须在定义时赋值,将变量与引用绑定。工具

那你有没有想过,上面的定义正确吗?编译器是如何解释引用的?spa

这里先给出引用的本质定义,后面咱们再进一步论证。设计

1,引用实际是经过指针实现的。指针

2,引用是一个常量指针。code

3,引用在内存中占4个字节。orm

4,在对引用定义时,须要对这个常量指针初始化。htm

2、探究本质

咱们从最简单的变量的定义开始,看编译器会作哪些事情。

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

没错,没有任何差异,定义一个引用和一个指针的汇编代码彻底一致!

3、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

复制代码

上面代码再次证实了,引用与指针的行为彻底一致,只是编译器在编译时对引用做了更严格的限制。

4、引用占多大的内存空间

由于在在表达式中,使用引用实际上就像使用变量自己同样,因此直接用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/rollenholt/articles/1907408.html

做者:☆Ronny丶

出处:http://www.cnblogs.com/ronny/

本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。

相关文章
相关标签/搜索