`

C++引用的本质 --另一种指针

    博客分类:
  • C++
c++ 
阅读更多

文章来源:http://www.itnose.net/detail/6033934.html 更多文章:http://www.itnose.net/type/55.html

本文不探讨罗列引用的概念,什么函数传参之类的,这些基础概念和用法很容易搜到~!

本文主要探讨引用和指针在C语言的下一层??即汇编或者确切的说是伪汇编(AT&T伪汇编都一样的代码,你指望下层x86汇编还能不一样么~)??的实现过程,来摸索一下他们的特点与本质。

 

首先,引用(Reference)在C中没有,是C++ 才有的概念~! 要用g++编译器。


定义:引用就是某个目标变量的“别名”(alias)

 

在我看来,这个“目标变量”也要加上引号,看“目标变量”到底是怎么定义的了。如果“目标变量”由变量名和值组成,那引用应该是不包含“变量名”这部分的,说白了,觉得他就是一个“新变量”,只是他和原变量的“值”(即,目标地址,存储内容)是共用的。

 

 

实例测试:

 

 引用,另一种,c++,指针,本质0

 

用g++编译,gdb调试:

 引用,另一种,c++,指针,本质1

 

可以看到,让refa引用a的过程,其实就是提取地址(lea),并且占用了栈空间。和指针的实现是一模一样的。不管你“理论上”怎么说,至少在实现上(至少在linux的实现上),他就是指针。

 

可以看到,操作都是直接或者间接的对a的原地址0x10(%esp)进行操作,这个没什么问题。但是说引用不占地址是错误的,作为一个“指针”他至少占用4字节吧~!

这是代码后续的赋值操作:

Breakpoint 2, main () at ref2.cpp:13

13              a= 2;

1: x/i $pc

=> 0x804868d <main()+153>: movl   $0x2,0x10(%esp)

 

Breakpoint 3, main () at ref2.cpp:18

18              refa= 3;

1: x/i $pc

=> 0x8048705 <main()+273>: mov   0x14(%esp),%eax

(gdb) si

0x08048709     18              refa = 3;

1: x/i $pc

=> 0x8048709 <main()+277>: movl  $0x3,(%eax)

 

22              *ptra= 3;

1: x/i $pc

=> 0x804877f <main()+395>:  mov   0x18(%esp),%eax

(gdb) si

0x08048783     22              *ptra = 3;

1: x/i $pc

=> 0x8048783 <main()+399>: movl  $0x3,(%eax)

 

可以看到引用和指针,从定义到赋值,实现都是一样的。

 


 

 

 

 

 

虽然引用和指针的意义,认为差不多,但是使用方法还是有差别的,想获得右值,引用直接用变量名,指针要加*操作符。而对引用使用*操作是不允许的。 

 

另外,不同于指针,引用在声明的时候必须初始化,

但引用可能只能一次初始化而不能改变引用“目标”吗?

至少通过如下方法是不能的:

int a = 1;

int b = 2;

int &refa = a;

refa =  b;
这相当于赋值为b,即赋值为2,连a的值都会变成2.
&refa = &b;

也是不可能的,因为&refa不是左值。

refa = &b;

更不对了,因为这也相当于赋值,不过不是2了,是b的地址(打印成10进制,类似于-1075934160这种),并且,需要强制转换:

refa = (int)&b;

 

 

说再多都是YY,实践出真知~!

围绕我的”引用即指针“的理念,再做一个摸索,既然认为引用是指针了,那么sizeof任何指针,包括double的,肯定都是4(我的32位机)。我定义一个double的引用,看看sizeof结果如何(右侧为输出结果):

引用,另一种,c++,指针,本质2

这个结果倒是没夸张到直接让ref变成pointer。sizeof(refd)还是按普通的double来算大小,而不是直接按指针来算的。但是也情有可原吧,都说了,虽然他的底层实现和指针一样,但是sizeof()需要的是返回类型,它的返回类型??即”操作级别“,还是比指针要低的。

 

最后:到底怎样理解引用更好?

首先,不太同意“引用就是一个别名,不占用内存空间“的说法,至少这句话可以再严谨点??”引用不为值再去开辟一个地址空间,但是其本身要占用空间~!“

 

奇了怪了,引用确实占用栈空间,也确实是存储了目标变量的地址~~~那既然有空间,就应该和指针一样,我改变你的值不就等于改变你的指向了么?

但是,因为它和指针不在同一个“操作级别”上,它的”值“又不是地址,也不能像指针那样改变他的指向。

(“操作级别”是通过存储内容来判定的,比如普通变量的存储内容是“值”,而指针的存储内容是“地址”,可以通过指针独特的“*”操作来判断这个“级别”)

 

个人倾向于认为引用本身就是一种指针,至于他又不能像指针一样进行重定向等操作,觉得这些完全是语言级别或者说编译器的刻意限制,只是一种规则,没有其他原因。

再次怀疑人生??编译器的本质如何?到底什么叫做编程语言?各层语言界限如何?从这么多的实践操作经验来总结,似乎也逐渐理解了些,如果再去看看《编译原理》,或许会有所收获。

 

本文不探讨罗列引用的概念,什么函数传参之类的,这些基础概念和用法很容易搜到~!

本文主要探讨引用和指针在C语言的下一层??即汇编或者确切的说是伪汇编(AT&T伪汇编都一样的代码,你指望下层x86汇编还能不一样么~)??的实现过程,来摸索一下他们的特点与本质。

 

首先,引用(Reference)在C中没有,是C++ 才有的概念~! 要用g++编译器。


定义:引用就是某个目标变量的“别名”(alias)

 

在我看来,这个“目标变量”也要加上引号,看“目标变量”到底是怎么定义的了。如果“目标变量”由变量名和值组成,那引用应该是不包含“变量名”这部分的,说白了,觉得他就是一个“新变量”,只是他和原变量的“值”(即,目标地址,存储内容)是共用的。

 

 

实例测试:

 引用,另一种,c++,指针,本质3

 

用g++编译,gdb调试:

 引用,另一种,c++,指针,本质4

 

可以看到,让refa引用a的过程,其实就是提取地址(lea),并且占用了栈空间。和指针的实现是一模一样的。不管你“理论上”怎么说,至少在实现上(至少在linux的实现上),他就是指针。

 

可以看到,操作都是直接或者间接的对a的原地址0x10(%esp)进行操作,这个没什么问题。但是说引用不占地址是错误的,作为一个“指针”他至少占用4字节吧~!

这是代码后续的赋值操作:

Breakpoint 2, main () at ref2.cpp:13

13              a= 2;

1: x/i $pc

=> 0x804868d <main()+153>: movl   $0x2,0x10(%esp)

 

Breakpoint 3, main () at ref2.cpp:18

18              refa= 3;

1: x/i $pc

=> 0x8048705 <main()+273>: mov   0x14(%esp),%eax

(gdb) si

0x08048709     18              refa = 3;

1: x/i $pc

=> 0x8048709 <main()+277>: movl  $0x3,(%eax)

 

22              *ptra= 3;

1: x/i $pc

=> 0x804877f <main()+395>:  mov   0x18(%esp),%eax

(gdb) si

0x08048783     22              *ptra = 3;

1: x/i $pc

=> 0x8048783 <main()+399>: movl  $0x3,(%eax)

 

可以看到引用和指针,从定义到赋值,实现都是一样的。

 


 

 

 

 

虽然引用和指针的意义,认为差不多,但是使用方法还是有差别的,想获得右值,引用直接用变量名,指针要加*操作符。而对引用使用*操作是不允许的。 

另外,不同于指针,引用在声明的时候必须初始化,

但引用可能只能一次初始化而不能改变引用“目标”吗?

至少通过如下方法是不能的:

int a = 1;

int b = 2;

int &refa = a;

refa =  b;
这相当于赋值为b,即赋值为2,连a的值都会变成2.
&refa = &b;

也是不可能的,因为&refa不是左值。

refa = &b;

更不对了,因为这也相当于赋值,不过不是2了,是b的地址(打印成10进制,类似于-1075934160这种),并且,需要强制转换:

refa = (int)&b;

 


围绕我的”引用即指针“的理念,再做一个摸索。既然认为引用是指针了,那么sizeof任何指针,包括double的,肯定都是4(我的32位机)。我定义一个double的引用,看看sizeof结果如何(右侧为输出结果):

引用,另一种,c++,指针,本质5

这个结果倒是没夸张到直接让ref变成pointer。sizeof(refd)还是按普通的double来算大小,而不是直接按指针来算的。但是也情有可原吧,都说了,虽然他的底层实现和指针一样,但是sizeof()需要的是返回类型,它的返回类型??即”操作级别“,还是比指针要低的,和普通的变量相仿。

 

最后:到底怎样理解引用更好?

首先,不太同意“引用就是一个别名,不占用内存空间“的说法,至少这句话可以再严谨点??”引用不为值再去开辟一个地址空间,但是其本身要占用空间~!“

 

奇了怪了,引用确实占用栈空间,也确实是存储了目标变量的地址~~~那既然有空间,就应该和指针一样,我改变你的值不就等于改变你的指向了么?

但是,因为它和指针不在同一个“操作级别”上,它的”值“又不是地址,也不能像指针那样改变他的指向。

(“操作级别”是通过存储内容来判定的,比如普通变量的存储内容是“值”,而指针的存储内容是“地址”,可以通过指针独特的“*”操作来判断这个“级别”)

 

个人倾向于认为引用本身就是一种指针,至于他又不能像指针一样进行重定向等操作,觉得这些完全是语言级别或者说编译器的刻意限制,只是一种规则,没有其他原因。

再次怀疑人生??反正翻译成下层的东西,都是那点破事,转换成最后就是一些地址一些寄存器,你能找到地址你就能改(不能改的话,又是哪层编译器或者汇编器限制你的呢?)~!那么,编译器的本质如何?到底什么叫做编程语言?各层语言界限如何?从这么多的实践操作经验来总结,似乎也逐渐理解了些,如果此时再去看看《编译原理》,或许会有所收获。

 

 

完~!

 

------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ---------------

OTHER:

------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ---------------

写的是关于引用的,但是通过用gdb调试,个人还是有其他方面的收获:

例如,AT&T汇编中括号的含义,目测带括号是取地址,不带括号是原寄存器,好像和之前《计算机组成原理》的伪指令规则差不多。

对比:

存入数值到eax寄存器??用%eax。

存入数值到eax寄存器所储存的内存地址处??用(%eax)。

esp的操作同样如此:比如0x18(%esp)应该是从esp取出内存地址,再加上0x18偏移量。

 

还有,之前看linux的伪汇编,esp一般都是不变的,变的是偏移值,使用类似于0x1c(%esp)的形式进行操作。

 

每次编译运行,esp起始都是230结尾的(系统决定,具体:0xbffff230),但是通过本例观察,说esp不变是不准确的,执行系统调用,涉及各种库的时候,一直在变:从230到22c、228、224。。。等于栈下移了?在同一函数内不移,切换了才移?

引用,另一种,c++,指针,本质6

也许试试嵌套个函数什么的也会有发现~

关于栈指针怎么跳转,甚至发生函数跳转时十几个寄存器到底保存上下文需要几个,而这几个压栈又是怎么压的,有一个规则,按顺序压,按倒序取?这又是另外一篇日志要探索的事情了。

其他未完成作业:

 

看看const的实现又是怎样的,是否有什么特殊的方法规定”只读“,比如转存寄存器之类的。

使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

(待验证)

 

分享到:
评论

相关推荐

    ListView上下翻页效果.zip

    ListView上下翻页效果

    Android项目之——漂亮的平台书架.zip

    Android项目之——漂亮的平台书架

    TestBrightness2.zip

    TestBrightness2

    00_Método_toBands.ipynb

    gee python 教程(西班牙语)

    (源码)基于Linux和GTK的系统监控与图形化显示.zip

    # 基于Linux和GTK的系统监控与图形化显示 ## 项目简介 本项目旨在通过分析Linux系统中的proc目录,提取并展示系统的关键信息,包括系统概况、进程信息和内存使用情况。通过使用GTK库,项目提供了一个图形化的用户界面,使用户能够直观地查看和监控系统的实时状态。 ## 项目的主要特性和功能 1. 系统信息展示 显示内核版本、系统启动时间等基本信息。 提供系统的主机名、CPU详细参数等信息。 2. 进程信息展示 显示所有进程的摘要信息,包括PID、CPU和内存使用率。 支持根据CPU使用率、内存使用率等参数对进程进行排序。 3. 内存信息展示 展示系统的内存使用情况,包括总内存、可用内存等详细参数。 4. 动态刷新 系统信息、进程信息和内存信息能够实时动态刷新,确保用户获取最新的系统状态。 5. 图形化界面 使用GTK库创建直观的图形界面,方便用户查看和操作。

    纯c语言迷宫源码.rar

    纯c语言迷宫源码

    c语言通讯录管理系统源码.rar

    c语言通讯录管理系统源码

    基于树莓派和GPT实现的多功能语音家庭助手

    功能列表 支持多种唤醒方式:语音唤醒,局域网消息唤醒,外设模块唤醒,远程唤醒 语音端点检测:自动检测语音截止点 语音识别:支持在线与离线双模式 文字转语音:舒适的人声 接续对话:完成交互对话全程只需唤醒一次 支持对话中断:可在任意时刻打断对话,重新提问 双引擎可选交互:接入GPT/星火大模型,支持聊天上下文,具有互联网搜索能力,并适时总结对话 聊天记忆:在程序结束后保存聊天内容,重新运行时自动加载 通知播报:手机上接收的消息(熄屏时)以自定义格式播报 音乐播放:获取QQ音乐个性推荐,支持调整音量,切换,暂停 音频闪避:在聊天交互/通知播报时自动减小音乐音量 日程设定:支持设定闹钟/倒计时,以及提醒事项 WebUI调参:可通过电脑和手机登录网页调参 外设控制:支持接入自定义设备(MQTT协议),配置相关文件可实现自动化 自动化智能家居:传入自定义状态,支持自定义场景触发自定义动作 远程控制:支持广域网MQTT设备控制 HomeAssistant:支持通过API控制HA下的设备

    c语言实现类似弹力球效果.rar

    c语言实现类似弹力球效果

    c语言实现的汉诺塔演示程序.rar

    c语言实现的汉诺塔演示程序

    c语言连连看游戏源码.rar

    c语言连连看游戏源码

    (源码)基于Arduino框架的自动称重系统.zip

    # 基于Arduino框架的自动称重系统 ## 项目简介 本项目是一个基于Arduino框架的自动称重系统。它利用Arduino硬件和Adafruit的ADS1115 ADC(模数转换器)库,实现了从负载单元读取重量数据并通过串行通信将数据传输到PC或其他设备的功能。项目还包含了LCD屏幕显示和LED指示灯的控制,以及对数据库的操作和Web交互的支持。 ## 项目的主要特性和功能 1. 硬件连接与通信: 项目使用了Arduino和ADS1115 ADC之间的串行通信,实现了从负载单元读取重量数据的功能。 2. 数据处理: 通过ADC读取的重量数据被处理并转换为可读的数值,然后通过串行端口发送到PC或其他设备。 3. 用户界面: 包含了LCD屏幕显示和LED指示灯的控制,用于实时显示重量数据或指示重量状态。 4. 数据库操作: 项目支持通过串行通信与数据库交互,实现数据的存储和查询。

    双鱼林jsp版超市信息管理系统.rar

    双鱼林jsp版超市信息管理系统

    C语言课程设计(成绩管理系统)源程序.zip

    C语言课程设计(成绩管理系统)源程序

    (源码)基于深度学习的投资策略优化系统.zip

    # 基于深度学习的投资策略优化系统 ## 项目简介 本项目是一个基于深度学习的投资策略优化系统,旨在通过分析和优化金融数据来提升投资决策的准确性和效率。项目涵盖了从数据获取、预处理、模型训练到结果评估的全流程,为投资者提供了一套完整的工具链。 ## 项目的主要特性和功能 1. 数据获取与处理 通过phase0.py获取金融数据。 使用phase1.py进行数据预处理和特征生成。 利用labelbasedgraph.py和labelbasedreturn.py进行数据标签计算。 2. 模型训练与评估 使用phase2.py进行模型训练和评估。 支持多种深度学习模型,如GraphCNN.py和MLP.py。 通过process.py管理模型训练和验证流程。 3. 结果可视化与分析 使用vision.py进行模型性能的可视化和评估。

    c语言课程设计-产品管理系统.zip

    c语言课程设计-产品管理系统

    技术资料分享BMP图片文件详解很好的技术资料.zip

    技术资料分享BMP图片文件详解很好的技术资料.zip

    C#ASP.NET手机端H5会议室预约系统源码 手机版会议室预约源码数据库 SQL2008源码类型 WebForm

    ASP.NET手机端H5会议室预约系统源码 手机版会议室预约源码 一、源码介绍 H5手机版会议室预约系统是一个高效快速便利的内部预约平台,让需要预定会议室的人能通过这个平 台发布预定,没有预定的人也能通过平台查看他人预定。通过后台添加账号即可登录预约平台,发布会 议室预定。 二、主要功能 后台管理包括 会议室信息管理,预约信息管理,用户信息管理。 前台手机版预约系统包括 日历查看预定信息,点击进入所选日期详细预约信息,预定会议室,我的预 约等功能模块。 后台采用模型管理功能可以使用后台对表结构进行维护,方便二次开发。 后台也可以增加部门,实现各部门之间管理员查看各自部门预约信息,用户信息等功能。

    http服务器的实现.zip

    http服务器的实现

Global site tag (gtag.js) - Google Analytics