几个问题:
1. 引用变量占有内存空间吗?
2. 引用是怎样工作的?
3. 指针是怎样工作的?
4. 引用和指针有什么区别?
1. 何为引用
《C++ Primer》里面是这样说的“引用(Reference)就是对象的另一个名字,引用只是它绑定的对象的另一个名字,作用在引用上的所有操作事实上都是作用在该引用绑定的对象上”,这句话概括得很彻底
2. 引用占有内存空间吗?
一段C++代码:
// Reference_Pointer_Local
#include <cstdio>
int main() {
int a = 100;
int& ref = a;
int* ptr = &a;
printf("%d %d %d\n", a, ref, *ptr);
return 0;
}
使用 /FAs 编译选项,其汇编代码:
PUBLIC _main
EXTRN _printf:NEAR
_DATA SEGMENT
$SG530 DB '%d %d %d', 0aH, 00H
_DATA ENDS
_TEXT SEGMENT
_a$ = -8
_ref$ = -12
_ptr$ = -4
_main PROC NEAR
; 4 : int main() {
push ebp
mov ebp, esp
sub esp, 12 ; 建立堆栈,预留12个字节
; 5 : int a = 100;
mov DWORD PTR _a$[ebp], 100 ; [ebp-8]是a的内存地址
; 6 : int& ref = a;
lea eax, DWORD PTR _a$[ebp] ; 获得a的内存地址
mov DWORD PTR _ref$[ebp], eax ; [ebp-12]是 ref 的内存地址,
; 保存的内存是 a 的内存地址
; 7 : int* ptr = &a;
lea ecx, DWORD PTR _a$[ebp] ; 获得a的内存地址
mov DWORD PTR _ptr$[ebp], ecx ; [ebp-4]是 ref 的内存地址,
; 保存的内存是 a 的内存地址
; 8 : printf("%d %d %d\n", a, ref, *ptr);
mov edx, DWORD PTR _ptr$[ebp]
mov eax, DWORD PTR [edx] ; 指针ptr间接获得a的值
push eax
mov ecx, DWORD PTR _ref$[ebp]
mov edx, DWORD PTR [ecx] ; 引用ref间接获得a的值
push edx
mov eax, DWORD PTR _a$[ebp]
push eax
push OFFSET FLAT:$SG530
call _printf
add esp, 16 ; 00000010H
; 9 : return 0;
xor eax, eax
; 10 : }
mov esp, ebp
pop ebp
ret 0 ; 恢复堆栈
_main ENDP
_TEXT ENDS
END
汇编代码很清楚地告诉我们,引用也占有内存空间,只不过在这块内存空间上保存的是绑定对象的内存地址,这点与指针很相似!
3. 引用和指针的工作机制
一段C++代码:
// swap_ref_ptr.cpp
#include <cstdio>
void swap_reference(int& ref_x, int& ref_y) {
int temp = ref_x;
ref_x = ref_y;
ref_y = temp;
return ;
}
void swap_pointer(int* ptr_x, int* ptr_y) {
int temp = *ptr_x;
*ptr_x = *ptr_y;
*ptr_y = temp;
return ;
}
int main() {
int x = 4;
int y = 9;
swap_reference(x, y);
swap_pointer(&x, &y);
return 0;
}
在这个程序中,引用和指针都做了相同的一件事,那就是交换两个数,这里先给出main函数调用swap_reference时栈的表示图(调用swap_pointer时同理):
使用 /FAs 编译选项,得到的汇编代码如下:
PUBLIC ?swap_reference@@YAXAAH0@Z ; swap_reference
_TEXT SEGMENT
_ref_x$ = 8
_ref_y$ = 12
_temp$ = -4
?swap_reference@@YAXAAH0@Z PROC NEAR ; swap_reference
; 4 : void swap_reference(int& ref_x, int& ref_y) {
push ebp
mov ebp, esp
push ecx
; 5 : int temp = ref_x;
mov eax, DWORD PTR _ref_x$[ebp] ; [ebp+8]保存的是x的内存地址,
; 还记得在main函数中调用swap_reference时压入的ECX吗
mov ecx, DWORD PTR [eax] ; ECX 获得x的值
mov DWORD PTR _temp$[ebp], ecx ; x的值暂存于temp的内存空间中,
; 编译器为temp分配了一块栈空间
; 6 : ref_x = ref_y;
mov edx, DWORD PTR _ref_x$[ebp] ; 可以理解为edx指向x
mov eax, DWORD PTR _ref_y$[ebp] ; 可以理解为eax指向y
mov ecx, DWORD PTR [eax]
mov DWORD PTR [edx], ecx ; 间接地赋值,其结果是直接影响原x,y的值
; 7 : ref_y = temp;
mov edx, DWORD PTR _ref_y$[ebp]
mov eax, DWORD PTR _temp$[ebp]
mov DWORD PTR [edx], eax
; 8 : return ;
; 9 : }
mov esp, ebp
pop ebp
ret 0
?swap_reference@@YAXAAH0@Z ENDP ; swap_reference
_TEXT ENDS
PUBLIC ?swap_pointer@@YAXPAH0@Z ; swap_pointer
_TEXT SEGMENT
_ptr_x$ = 8
_ptr_y$ = 12
_temp$ = -4
?swap_pointer@@YAXPAH0@Z PROC NEAR ; swap_pointer
; 11 : void swap_pointer(int* ptr_x, int* ptr_y) {
push ebp
mov ebp, esp
push ecx
; 12 : int temp = *ptr_x;
mov eax, DWORD PTR _ptr_x$[ebp] ; [ebp+8]保存的是x的内存地址,
; 还记得在main函数中调用swap_pointer时压入的EAX吗
mov ecx, DWORD PTR [eax]
mov DWORD PTR _temp$[ebp], ecx
; 13 : *ptr_x = *ptr_y;
mov edx, DWORD PTR _ptr_x$[ebp] ; 可以理解为edx指向x
mov eax, DWORD PTR _ptr_y$[ebp] ; 可以理解为eax指向y
mov ecx, DWORD PTR [eax]
mov DWORD PTR [edx], ecx ; 间接地赋值,其结果是直接影响原x,y的值
; 14 : *ptr_y = temp;
mov edx, DWORD PTR _ptr_y$[ebp]
mov eax, DWORD PTR _temp$[ebp]
mov DWORD PTR [edx], eax
; 15 : return ;
; 16 : }
mov esp, ebp
pop ebp
ret 0
?swap_pointer@@YAXPAH0@Z ENDP ; swap_pointer
_TEXT ENDS
PUBLIC _main
_TEXT SEGMENT
_x$ = -4
_y$ = -8
_main PROC NEAR
; 18 : int main() {
push ebp
mov ebp, esp
sub esp, 8
; 19 : int x = 4;
mov DWORD PTR _x$[ebp], 4
; 20 : int y = 9;
mov DWORD PTR _y$[ebp], 9
; 21 : swap_reference(x, y);
lea eax, DWORD PTR _y$[ebp] ; 获得y的内存地址
push eax
lea ecx, DWORD PTR _x$[ebp] ; 获得x的内存地址
push ecx
call ?swap_reference@@YAXAAH0@Z ; 调用时将返回地址Ret压入堆栈,ESP减4
add esp, 8
; 22 : swap_pointer(&x, &y);
lea edx, DWORD PTR _y$[ebp] ; 获得y的内存地址
push edx
lea eax, DWORD PTR _x$[ebp] ; 获得x的内存地址
push eax
call ?swap_pointer@@YAXPAH0@Z ; 调用时将返回地址Ret压入堆栈,ESP减4
add esp, 8
; 23 : return 0;
xor eax, eax
; 24 : }
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
在这里,我们看到引用和指针都通过间接的操作来完成交换值的操作,在最简单的情况下,引用对其绑定的对象的赋值操作的次数是2次,第一次从引用本身的内存中取出对象的地址,再通过对指定地址的内存(被绑定的对象)操作来直接影响对象,比如说:
// Reference_Pointer_Local
#include <cstdio>
int main() {
int a = 100;
int& ref = a;
ref = 5;
return 0;
}
其汇编代码:
PUBLIC _main
_TEXT SEGMENT
_a$ = -4
_ref$ = -8
_main PROC NEAR
; 4 : int main() {
push ebp
mov ebp, esp
sub esp, 8
; 5 : int a = 100;
mov DWORD PTR _a$[ebp], 100 ; [ebp-4]是a的地址
; 6 : int& ref = a;
lea eax, DWORD PTR _a$[ebp]
mov DWORD PTR _ref$[ebp], eax ; [ebp-8]是ref的内存地址,保存的是 a 的地址
; 7 : ref = 5;
mov ecx, DWORD PTR _ref$[ebp] ; 先从ref的内存中取出 a 的地址
mov DWORD PTR [ecx], 5 ; 再对 a 进行间接的操作,直接影响了 a 的值
至于指针的话,自己去探索一下,其工作的机制是类似的
4. 引用和指针的区别
引用和指针都是通过间接的手段直接影响原对象的。
1. 对于指针来说,指针可以不指向任何一个对象(指向NULL),而引用自初始化后就与某个对象绑定了,引用必须在定义的时候就进行初始化,因为引用本身不能与空对象进行绑定
2. 在某种程度上说,比如测试,引用的代码效率比指针高,因为引用总是与某个对象进行绑定,而指针则不然(这点摘自《More Effective C++》Item M1)
3. 引用自定义后就“从一而终”地与某个对象绑定,任何试图让其绑定其它对象的做法都是被禁止的;而指针是相当灵活的,它可以改变其指向的对象,在指针范畴上,不存在“绝对的绑定”
总的来说,如果你需要在任何时刻都能够改变指向的对象时,应该用指针而不是引用;如果你希望总是指向某个对象并且这个指向关系是“从一而终”的,那么你应该使用引用而不是指针