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