简单比较一下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 这么一句代码

 

posted @ 2020-04-07 23:44  鸿钧三清  Views(260)  Comments(0Edit  收藏  举报