zoukankan      html  css  js  c++  java
  • 简单比较一下C++中的引用和指针

    以前刚学C++是书上说引用是变量的别名,这个说法感觉不能很好的理解引用的本质,加上C++和java等其他语言的引用也不完全一样感觉还是要比较实际代码才行。

    在这里简单比较一下引用和指针在汇编代码上的的区别:

    编译器: x86-64 gcc 9.3
    c++标准: -std=c++17
    不做任何优化

    在线编译器网址:https://godbolt.org/


    比较对象: int& and int * const 两种类型
    这里不选取普通指针的原因是普通的指针可以在声明的时候不初始化, 为了尽可能小的缩小二者的差距,这里采用const修饰指针本身使其不可改变其能指。

    1.在作为参数传递时
    void pointer_const(int * const);
    void reference(int&);
    二者生成的汇编代码一致如下:
            push    rbp
            mov     rbp, rsp               // 函数压栈
            mov     QWORD PTR [rbp-8], rdi // 传参
            nop
            pop     rbp
            ret                            // 返回
            
    2.在被初始化以及对对象进行操作时
    int c = 100;            // .long   100
    // 在全局进行初始化
    int * const ptr = &c;   // 未生成代码
    int& ref = c;            // .quad   c
    
    void test() {
        // 在函数内部用值初始化
        int * const ptr1 = &c; //         mov     QWORD PTR [rbp-8], OFFSET FLAT:c
        int& ref1 = c;      //               mov     QWORD PTR [rbp-16], OFFSET FLAT:c
        // 可见以上二者的汇编代码是一样的
        
        // 同种类型变量的初始化
        int * const ptr2 = ptr;                
        int& ref2 = ref;        //                 代码同上,未发生改变
    
        ref++; //        mov     eax, OFFSET FLAT:c
               //         mov     edx, DWORD PTR [rax]
               //          add     edx, 1
               //        mov     DWORD PTR [rax], edx
               
        (*ptr)++; //        mov     eax, DWORD PTR c[rip]
                  //        add     eax, 1
                  //        mov     DWORD PTR c[rip], eax
                  
        // 可见引用的操作相对于指针来说多了一步
        
        // 生成新的变量
        int y = *ptr;
            //    mov     eax, DWORD PTR c[rip]
            //    mov     DWORD PTR [rbp-4], eax
        int x = ref;
                // mov     eax, OFFSET FLAT:c
                // mov     eax, DWORD PTR [rax]
                // mov     DWORD PTR [rbp-8], eax
        // 可见引用还是多了一步操作
    }
    
    
    // 接下来我们将c变为局部变量再进行相同的操作
    
    void test() {
        int c = 100;             //         mov     DWORD PTR [rbp-68], 100
        
        int * const ptr = &c;   //         lea     rax, [rbp-68]
                                //           mov     QWORD PTR [rbp-8], rax
                                
        int& ref = c;            //        同指针
    
        int * const ptr2 = ptr; //  同上
        int& ref2 = ref;        //  同上
    
        int xx = ref;
        int yy = *ptr;
        //         mov     rax, QWORD PTR [rbp-16]
        //         mov     eax, DWORD PTR [rax]
        //         mov     DWORD PTR [rbp-20], eax
        // 一样,这里只放一份代码
        
        
        int y = *ptr;
        int x = ref;
        //        mov     rax, QWORD PTR [rbp-8]
        //        mov     eax, DWORD PTR [rax]
        //        mov     DWORD PTR [rbp-28], eax
        //     一样, 这里只放一份代码
        
        // 可见二者并没有什么区别
    }
    
    // 当我们把引用和指针变为函数的参数在对其进行操作时
    
    void test(int& x, int * const ptr) {
        x++;
        (*ptr)++;
        /*  // 代码一样只放一份
            mov     rax, QWORD PTR [rbp-8]
            mov     eax, DWORD PTR [rax]
            lea     edx, [rax+1]
            mov     rax, QWORD PTR [rbp-8]
            mov     DWORD PTR [rax], edx
        */
    }
    
    // 当我们使用class在进行如上test时:
    
    int c = 100; //         .long   100
    
    class A {
    
        static int * const static_const_ptr;
        static int & static_reference;
        
        int * const ptr;
        int& ref;
    
        public:
            A(int* ptr, int & ref):ptr(ptr),ref(ref) {}
            void test_ptr();
            void test_ref();
            static void test_static_ptr();
            static void test_static_ref();
    };
    
    int * const A::static_const_ptr = &c; //         .quad   c
    int& A::static_reference = c;         //         .quad   c
    
    void A::test_ptr() {
        (*ptr)++;
        /*
        A::test_ptr():
            push    rbp
            mov     rbp, rsp
            mov     QWORD PTR [rbp-8], rdi
            mov     rax, QWORD PTR [rbp-8]
            mov     rax, QWORD PTR [rax]
            mov     edx, DWORD PTR [rax]
            add     edx, 1
            mov     DWORD PTR [rax], edx
            nop
            pop     rbp
            ret
        */
    }
    
    void A::test_ref() {
        ref++;
        /*
        A::test_ref():
            push    rbp
            mov     rbp, rsp
            mov     QWORD PTR [rbp-8], rdi
            mov     rax, QWORD PTR [rbp-8]    // 数字是寻址的问题,改变两个成员变量的位置数字就会改变
            mov     rax, QWORD PTR [rax+8]
            mov     edx, DWORD PTR [rax]
            add     edx, 1
            mov     DWORD PTR [rax], edx
            nop
            pop     rbp
            ret
        */
        
    }
    // 可见并无什么区别
    
    
    void A::test_static_ptr() {
        (*static_const_ptr) ++;
        /*
        A::test_static_ptr():
            push    rbp
            mov     rbp, rsp
            mov     eax, DWORD PTR c[rip]
            add     eax, 1
            mov     DWORD PTR c[rip], eax
            nop
            pop     rbp
            ret
        */
    }
    
    void A::test_static_ref() {
        static_reference++;
        /*
        A::test_static_ref():
            push    rbp
            mov     rbp, rsp
            mov     eax, OFFSET FLAT:c
            mov     edx, DWORD PTR [rax]
            add     edx, 1
            mov     DWORD PTR [rax], edx
            nop
            pop     rbp
            ret
        */
    }
    
    // 可见和之前一样多了一步操作 mov     eax, OFFSET FLAT:c
    
    
    // 我们将int c改为结构体,使其结构更复杂,代码如下:
    
    struct A{
        int x, y;
    };
    
    
    void test(A* const ptr, A& ref) {
        //        push    rbp
        //          mov     rbp, rsp       压栈
        //        mov     QWORD PTR [rbp-8], rdi           可见PTR [rbp-8]  是ptr
        //          mov     QWORD PTR [rbp-16], rsi           可见PTR [rbp-16] 是ref
        
        
        ptr->x = 0; //   mov rax, QWORD PTR [rbp-8]   mov DWORD PTR [rax], 0     
        ref.x = 0;  //   mov rax, QWORD PTR [rbp-16]  mov DWORD PTR [rax], 0
        
        ptr->y = 0; //   mov rax, QWORD PTR [rbp-8]   mov DWORD PTR [rax+4], 0
        ref.y = 0;  //   mov rax, QWORD PTR [rbp-16]  mov DWORD PTR [rax+4], 0
        
        
        /* 返回
            nop
            pop     rbp
            ret
            发现并没有区别
        */
    }
    
    总结: 二者的主要区别在于在对全局或类的静态变量的引用的对象进行操作时会多出 mov eax, OFFSET FLAT:c 这么一句代码
  • 相关阅读:
    Reface.AppStarter 基本示例
    Reface.AppStarter 类型扫描 —— 获得项目中所有的实体类型
    多线程和异步有什么关联和区别?如何实现异步?
    事件总线功能库,Reface.EventBus 详细使用教程
    代理模式是什么?如何在 C# 中实现代理模式
    监听者模式在系统中的应用 —— 事件总线
    如何将 .NetFramework WebApi 按业务拆分成多个模块
    Reface.NPI 方法名称解析规则详解
    EF 太重,MyBatis 太轻,ORM 框架到底怎么选 ?
    Reface.AppStarter 框架初探
  • 原文地址:https://www.cnblogs.com/MasterYan576356467/p/12657155.html
Copyright © 2011-2022 走看看