在学c++的时候 我遇到的第一个问题就是这个引用,引用是什么东西,我的c++启蒙教科书是c++ primer plus,这本书上说的是:引用是已定义变量的别名,可以使用这个引用来表示这个变量;每当看到这句话的时候 我就有一种淡淡的的忧伤感,其实还是不懂, 这句话说的意思是说引用是个名字吗,那么引用占多大内存呢,我把这章从头看到尾 可惜对此只字不提, 由此可见写书者的诚意(至少我看不到), 本文就准备解析一下这个引用到底是个什么东东。
先说一下本文的结构,本文主要由三个问题1,引用占内存吗,多大 2.引用跟指针啥关系 3.为什么要发明引用呢?针对这三个问题本文希望从c逻辑和汇编底层层面进行剖析,如有疏漏,还请大家批评指教。
首先第一个问题是引用占内存吗:为了回答这个问题,我们可以用假设验证法;假设1,引用是个名字而已,不占内存,这个我觉得是有些书和老师要传达给我的想法(起码让我这样想了),2.引用占内存,内存大小和它所引用的变量的大小,一样大(这个听起来貌似也有点道理);假设3,引用跟指针一样大,在x86架构上是32位的,就像指针一样(这个是根据引用的作用跟指针相似推出的); 那么我们就写个程序来测一测哪个对喽:
#include <iostream> using namespace std; class Data { public: char a; char & b; Data():b(a) {} }; int main() { Data d; cout<<sizeof (d)<<endl; return 0; }
看上面的代码,这个对象呢,包含两种类型的变量,一个是char 型的变量另一个是它的引用,那就蛋疼了,这个对象到底是多大的呢;
如果假设1,成立那应该是1,如果假设2成立,那应该是2,如果假设三成立,应该是大于4的倍数也就是8(这里其实是内存对齐机制大家可以测试一个char 和一个int 组成的结构体是8,这个问题更有点意思以后再说) 嗯,那么经过我的测试,这个结果是8个字节,说明啊 这个char & b这个东西啊 占了四个字节 ,这里我们就庆幸没有int型做测试吧;嗯现在弄清了内存方面的事情,就开始思考这个引用变量是个什么东西了,
接着,我们就来看看这个引用它究竟是什么东西,这里我们会对一段cpp代码的结果反编译成汇编来分析分析;
先来一个测试代码
int main() { int a=10; int & b=a; b=20; return 0; }
这么小,能干啥事啊,别担心,虽然这个玩意儿连头文件都没带,但是却能干大事啊 把这段代码编译链接成exe,然后我们学一会儿黑客(黑的是自己),分析一下这个程序的汇编成分;
_main proc near var_8= dword ptr -8 var_4= dword ptr -4 argc= dword ptr 8 argv= dword ptr 0Ch envp= dword ptr 10h push ebp mov ebp, esp sub esp, 8 mov [ebp+var_4], 0Ah lea eax, [ebp+var_4] mov [ebp+var_8], eax mov ecx, [ebp+var_8] mov dword ptr [ecx], 14h xor eax, eax mov esp, ebp pop ebp retn _main endp
这是啥玩意啊,看不懂啊,看不懂没关系,我们可以把上面那个cpp代码换成指针版本的;如下
int main() { int a=10; int * b=&a; *b=20; return 0; }
功能一样是吧,那我们也来反编译一下;
_main proc near var_8= dword ptr -8 var_4= dword ptr -4 argc= dword ptr 8 argv= dword ptr 0Ch envp= dword ptr 10h push ebp mov ebp, esp sub esp, 8 mov [ebp+var_4], 0Ah lea eax, [ebp+var_4] mov [ebp+var_8], eax mov ecx, [ebp+var_8] mov dword ptr [ecx], 14h xor eax, eax mov esp, ebp pop ebp retn _main endp
有没有发现,两个汇编代码居然是一模一样啊,应该没有一点不一样的;虽然不明白它们是什么意思,但是好厉害的样子,起码能够证明引用和指针在底层实现上是等价的,这也就解释了为什么问题1里面引用的内存是4个字节了(X86 架构哦);对于这两段汇编代码(其实是一样的) 如果感兴趣的话,我们可以聊一聊 慢慢的有种渐入佳境的感觉了,下面我们就来从逻辑上分析一下,为什么要有这么一种引用类型呢;
其实这个问题,不是我能回答的,因为c++又不是我发明的,我哪里知道, 但是呢我们可以从编程语言的历史上推测(这是一种科学的猜测方法) , c语言有一个强大的东西叫指针,它的功能强大,又有多种变种,。拿到一个指针,便能操作那块内存了, 但是呢 指针太强大了,容易出错啊,怎么办呢,c++就说咱们来封装一个这个指针吧,虽然不能灭了它,但咱惹不起总躲得起吧; 于是 就发明出引用这种东西,从使用角度上讲增加了许多易用性呢;比如说对int a;操作 指针要 int *pa=a; * pa=123;而引用就不用* 了,而且引用必须在定义的时候赋初值,否则报错,而指针就没错 甚至有些连警告都没有,想想看这多危险啊, 还有引用一经指定对象便不能更改 相当于一个const指针 ,所以我们可以认为引用是对const指针的一种封装 ,好了引用有了 那就灭掉指针吧,不行,因为c++有一种妥协性,怎么说呢,引用受限是为了防止指针使用出错,但是由于指针太强大了,在一些需要指针出现的场合(比如遍历)引用又不能胜任,所以就又保留了指针; 在对指针的封装方面,java做到了,它直接把指针封装成名字了,让人感觉不到指针的存在,完全抛弃了指针 , 那就不能姓c了 于是就叫java吧;
经过上面放分析,我们知道引用就是对const指针的封装,为什么要封装,当然是第一安全,第二快速;就像栈和队列是对链表的封装一样,链表那么强大的东西,封装出来两个小东西,c也一样, 其他语言就是在逻辑层面封装了c , 懂了c再搞这些东西,岂不是 小儿科 ,要是不懂c 不懂内存,那这些我感觉就得跟着感觉走了,理解起来就不是推测而是猜测了; 有人觉得我不懂这些东西也行啊 会用就行了,那这是建立在记忆而不是理解的基础上,等到一段时间不用,再回头来看,我觉得如果之前理解过还是要回忆的快; 所以每当听到有人说c简单
我就觉得莫名其妙 这些东西不都是c玩剩下 的么;