• 由底层和逻辑说开去--c++之引用的深入剖析


         在学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玩剩下 的么;



  • 相关阅读:
    Java中断机制
    RPC原理
    synchronized和ReentrantLock的区别
    dubbo入门
    Zookeeper入门
    分布式事务
    Mysql索引会失效的几种情况
    java代码执行过慢的问题定位
    持续集成
    Mycat 数据库分库分表中间件
  • 原文地址:https://www.cnblogs.com/dragonfive/p/3265848.html
Copyright © 2020-2023  润新知