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

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

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

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

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

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

实例测试:

用g++编译,gdb调试:

可以看到,让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结果如何(右侧为输出结果):

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

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

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

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

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

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

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

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

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

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

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

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

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

实例测试:

用g++编译,gdb调试:

可以看到,让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结果如何(右侧为输出结果):

这个结果倒是没夸张到直接让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。。。等于栈下移了?在同一函数内不移,切换了才移?

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

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

其他未完成作业:

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

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

(待验证)

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

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

时间: 2024-10-13 11:17:14

C++引用的本质——另一种指针的相关文章

C++学习笔记----2.4 C++引用在本质上是什么,它和指针到底有什么区别

从概念上讲.指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变. 而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量). 在C++中,指针和引用经常用于函数的参数传递,然而,指针传递参数和引用传递参数是有本质上的不同的: 指针传递参数本质上是值传递的方式,它所传递的是一个地址值.值传递过程中,被调

什么时候使用引用?和什么时候使用指针

什么时候使用引用?和什么时候使用指针 1.问题提出: 当一个类的对象作为实参数传递时,使用值传递和引用传递有什么区别? 比如: DateType ExampleFun(CString &strFileName,...)与 DateType ExampleFun(CString strFileName,...) 解答之前,我们先来看2个基本的概念:形参和实参. ->通俗的讲:形参是形式上的参数,实参是实际的参数; ->详细的讲:形参只是对实参的一种抽象类型描述,只是声明一个函数(方法)能

C++中引用的本质分析

引用的意义 引用作为变量别名而存在,因此在一些场合可以代替指针 引用相对于指针来说具有更好的可读性和实用性 swap函数的实现对比: void swap(int* a, int* b) { int t = *a; *a = *b; *b = t; } void swap_yinyong(int& a,int& b) { int t = a; a = b; b = t; } int main() { int a = 1; int b = 2; printf("a = %d,b =

指针的指针,指针的引用(不是二维指针)

详解c++指针的指针和指针的引用 展示一下使用指针的指针和指针的引用修改传递给方法的指针,以便更好的使用它.(这里说的指针的指针不是一个二维数组) 为什么需要使用它们 当我们把一个指针做为参数传一个方法时,其实是把指针的复本传递给了方法,也可以说传递指针是指针的值传递. 如果我们在方法内部修改指针会出现问题,在方法里做修改只是修改的指针的copy而不是指针本身,原来的指针还保留着原来 的值.我们用下边的代码说明一下问题: int m_value = 1; void func(int *p) {

C++中引用的本质

一般的教材上讲到引用时,都是说"引用是对象的一个别名".我认为这种定义是不清晰的,不利于初学者理解引用.至少我自己曾经被这个定义困扰了一段时间.到底什么是"别名"? 实际上,引用的实质是位于xxxxxx地址上的一个xxxx类型的对象.比如教科书上常用的例子: int a = 5:           //不妨假设编译器将a分配到0x400000 int &b = a: 这里面b的准确意义就是,放在0x400000地址上的一个int类型对象.这里面包括了两重含

hadoop学习;自定义Input/OutputFormat;类引用mapreduce.mapper;三种模式

hadoop分割与读取输入文件的方式被定义在InputFormat接口的一个实现中,TextInputFormat是默认的实现,当你想要一次获取一行内容作为输入数据时又没有确定的键,从TextInputFormat返回的键为每行的字节偏移量,但目前没看到用过 以前在mapper中曾使用LongWritable(键)和Text(值),在TextInputFormat中,因为键是字节偏移量,可以是LongWritable类型,而当使用KeyValueTextInputFormat时,第一个分隔符前后

C 提高1 内存四区 变量本质 栈开口方向 指针铁律1

C 提高第一天复习 内存四区,变量常量的本质,函数调用模型,栈开口方向,指针铁律1,指针是一种数据类型 C 提高学员标准:写一个标准的冒泡排序 选择法或者冒泡法排序 在一个函数内排序 通过函数调用的方式排序 数组做函数参数的技术盲点和推演 #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int i = 0; int j = 0; int tmp = 0; int a[] = 

C++引用的本质

引用为变量提供别名,最重要的用途之一是在给函数传递参数方面 注意:引用作为函数的形参时不需要初始化! 引用作为变量的别名而存在,因此有一些场合可以代替指针 引用相当于指针来说具有更好的可读性和使用性 swap.cpp #include<stdio.h> int swap(int &a, int &b) { int temp; temp = b; b = a; a = temp; } int main(void) { int a = 1; int b = 2; swap(a, b

Excel vba引用工作表的三种写法

文章介绍vba引用工作表名称的三种不同写法. vba引用工作表是我们在学习VBA过程中很常用. 本文提供三种vba引用工作表的代码,通过这三种方式都可以实现vba引用工作表名. 方法一:Sheets(Sheet.Index) 方法二:Sheets(Sheets.Name) 方法三:Sheets.CodeName 下面是vba引用工作表的相关的一个截图,可以很直观的看到其使用. 原文地址:https://www.cnblogs.com/huhewei/p/9039168.html