从逆向分析角度看C++ 中的引用
2013-03-13 22:00
141 查看
几个问题:
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. 引用自定义后就“从一而终”地与某个对象绑定,任何试图让其绑定其它对象的做法都是被禁止的;而指针是相当灵活的,它可以改变其指向的对象,在指针范畴上,不存在“绝对的绑定”
总的来说,如果你需要在任何时刻都能够改变指向的对象时,应该用指针而不是引用;如果你希望总是指向某个对象并且这个指向关系是“从一而终”的,那么你应该使用引用而不是指针
相关文章推荐
- [c++学习笔记]反汇编角度看变量名和引用作为函数参数
- 从逆向分析角度看C++拷贝构造函数
- 从逆向分析角度看C++虚函数
- 从编译器角度理解C++中的引用和指针
- 从逆向分析角度看C++的析构函数
- 从汇编角度看c++引用(reference)
- c++ 引用底层实现
- C++中引用和指针的不同
- C++字符数组(定义和初始化、赋值和引用)
- c++中引用的一些研究
- 【C++基础学习】引用和指针、重载、函数、内存管理
- C++中引用与指针的区别(详细介绍)
- C++指针与Java引用的一处区别
- C++返回引用类型(一)
- C++指针与引用【转贴】
- C++中指针和引用的区别
- C++引用类型
- C++中的角度计算
- c++中三种参数引用方式