当前位置: 首页 > article >正文

38. C++ 引用的本质

1. C++ 引用的本质

1.1 引用的底层实现方式

引用被称为变量的别名,它不能脱离被引用对象独立存在,这是在高级语言层面的概念和理解,并未揭示引用的实现方式。常见错误说法是“引用“自身不是一个变量,甚至编译器可以不为引用分配空间。

存放的是被引用对象的地址。

实际上,引用本身是一个变量,只不过这个变量的定义和使用与普通变量有显著的不同。为了解引用变量底层实现机制,考查如下代码:

int i = 5;
int &ri = i;
ri = 8;

在Visual Studio 2017环境的debug模式调试代码,反汇编查看源码对应的汇编代码的步骤是:调试->窗口->反汇编,即可得到如下原码对应的汇编代码:

int i = 5;
00A013DE  mov        dword ptr [i],5      # 将文字常量5送入变量i

int &ri = i;
00A013E5  lea        eax,[i]              # 将变量i的地址送入寄存器eax
00A013E8  mov        dword ptr [ri],eax   # 将寄存器的内容(也就是变量i的地址)送入变量ri

ri = 8;
00A013EB  mov        eax,dword ptr [ri]   # 将变量ri的值送入寄存器eax
00A013EE  mov        dword ptr [eax],8    # 将数值8送入以eax的内容为地址的单元中

return 0;
00A013F4  xor        eax,eax

考查以上代码,在汇编代码中,ri的数据类型为dword,也就是说,ri要在内存中占据4个字节的位置。所以,ri的确是一个变量,它存放的是被引用对象的地址。由于通常情况下,地址是由指针变量存放的,那么,指针变量和引用变量有什么区别呢?使用指针常量实现上面的代码功能。考查如下代码:

int i = 5;
int* const pi = &i;
*pi = 8;

按照相同的方式,在VS2017中得到如下汇编代码:

int i=5;
011F13DE  mov         dword ptr [i],5  

int * const pi = &i;
011F13E5  lea         eax,[i]  
011F13E8  mov         dword ptr [pi],eax 

*pi = 8;
011F13EB  mov         eax,dword ptr [pi]  
011F13EE  mov         dword ptr [eax],8  

观察以上代码可以看出:
(1)只要将pi换成ri,所得汇编代码与第一段所对应的汇编代码完全一样。所以,引用变量在功能上等于一个指针常量,即一旦指向某一个单元就不能在指向别处。
(2)在底层,引用变量由指针按照指针常量的方式实现。

(3)引用是一种更安全的指针,因为引用必须初始化,不会出现空指针或野指针

(4) 存在多级指针,但不存在多级引用

1.2 高级语言层面引用与指针常量的关系

(1)在内存中都是占用4个字节(32bits系统中)的存储空间,存放的都是被引用对象的地址,都必须在定义的同时进行初始化。

(2)指针常量本身(以p为例)允许寻址,即&p返回指针常量(常变量)本身的地址,被引用对象用*p表示;引用变量本身(以r为例)不允许寻址,&r返回的是被引用对象的地址,而不是变量r的地址(r的地址由编译器掌握,程序员无法直接对它进行存取),被引用对象直接用r表示。

(3)凡是使用了引用变量的代码,都可以转换成使用指针常量的对应形式的代码,只不过书写形式上要繁琐一些。反过来,由于对引用变量使用方式上的限制,使用指针常量能够实现的功能,却不一定能够用引用来实现。

例如,下面的代码是合法的:

int i=5, j=6;
int* const array[] = {&i,&j};

而如下代码是非法的:

int i = 5, j = 6;
int& array[] = {i,j};

也就是说,数组元素允许是指针常量,却不允许是引用。C++语言机制如此规定,原因是避免C++语法变得过于晦涩。假如定义一个“引用的数组”,那么array[0]=8;这条语句该如何理解?是将数组元素array[0]本身的值变成8呢,还是将array[0]所引用的对象的值变成8呢?对于程序员来说,这种解释上的二义性对正确编程是一种严重的威胁,毕竟程序员在编写程序的时候,不可能每次使用数组时都要回过头去检查数组的原始定义。

补充:引用与数组之间的关系
  • 引用可以作为数组元素的别名

  • int a[3] = { 1, 2, 3 };
    int& b = a[0];
    b = 4;
    cout << a[0] << endl;
    
  • 引用只有在分配内存的时候才必须初始化

  • struct Test {
    	int& a;
    }
    cout << sizeof(Test) << endl; // 8
    Test test;
    cout << sizeof(test) << endl; // 报错
    
  • 不能建立引用数组

  • int a[3] = {1, 2, 3};
    int &b = a;
    

http://www.kler.cn/a/231848.html

相关文章:

  • 华为欧拉系统使用U盘制作引导安装华为欧拉操作系统
  • 定时器简介
  • 【项目开发】URL中井号(#)的技术细节
  • ARM(安谋) China处理器
  • 如何让手机ip变成动态
  • [ACTF2020]Upload 1--详细解析
  • 批量检测微信小程序是否封禁接口源码
  • JVM 性能调优 - Java 虚拟机内存体系(1)
  • OpenCV识别视频中物体运动并截取保存
  • netstat: 未找到命令
  • C++面试宝典第27题:完全平方数之和
  • 常用排序算法(Java版本)
  • 【数据结构】堆(创建,调整,插入,删除,运用)
  • 巴尔加瓦算法图解:算法运用。
  • css1字体属性
  • 【数据结构】二叉树的顺序结构及链式结构
  • [VulnHub靶机渗透] dpwwn: 1
  • 解析spritf和sscanf与模拟常用字符串函数strchr,strtok(二)
  • mysql的慢sql优化
  • (41)速算机器人
  • 工作与生活平衡:在生活中寻找和谐
  • Python 潮流周刊#38:Django + Next.js 构建全栈项目
  • uniapp小程序端使用计算属性动态绑定style样式踩坑
  • Docker安装ElasticSearch8.9.0
  • 【buuctf--被偷走的文件】
  • 美赛结束后,还可以转学术论文发表!