C++逆向分析——引用
void main() { int x = 1; int& ref = x; ref = 2; printf("%d \n", ref); return; }
反汇编代码:
int x = 1; 00724A5F C7 45 F4 01 00 00 00 mov dword ptr [x],1 int& ref = x; 00724A66 8D 45 F4 lea eax,[x] 00724A69 89 45 E8 mov dword ptr [ref],eax ref = 2; 00724A6C 8B 45 E8 mov eax,dword ptr [ref] 00724A6F C7 00 02 00 00 00 mov dword ptr [eax],2 printf("%d \n", ref); 00724A75 8B 45 E8 mov eax,dword ptr [ref] 00724A78 8B 08 mov ecx,dword ptr [eax] 00724A7A 51 push ecx 00724A7B 68 30 7B 72 00 push offset string "%d \n" (0727B30h) 00724A80 E8 4D C6 FF FF call _printf (07210D2h) 00724A85 83 C4 08 add esp,8
修改为指针,看看反汇编结果:
void main() { int x = 1; int* ref = &x; *ref = 2; printf("%d \n", *ref); return; }
是不是发现下面的反汇编结果和上面引用的一模一样!
int x = 1; 00B44A5F C7 45 F4 01 00 00 00 mov dword ptr [x],1 int* ref = &x; 00B44A66 8D 45 F4 lea eax,[x] 00B44A69 89 45 E8 mov dword ptr [ref],eax *ref = 2; 00B44A6C 8B 45 E8 mov eax,dword ptr [ref] 00B44A6F C7 00 02 00 00 00 mov dword ptr [eax],2 printf("%d \n", *ref); 00B44A75 8B 45 E8 mov eax,dword ptr [ref] 00B44A78 8B 08 mov ecx,dword ptr [eax] 00B44A7A 51 push ecx 00B44A7B 68 30 7B B4 00 push offset string "%d \n" (0B47B30h) 00B44A80 E8 4D C6 FF FF call _printf (0B410D2h) 00B44A85 83 C4 08 add esp,8
引用类型
引用类型就是变量的别名,其在初始化时必须要赋值。
// 基本类型
int
x =
1
;
int
& ref = x;
ref =
2
;
printf(
"%d \n"
,ref);
// 类
Person p;
Person& ref = p;
ref.x =
10
;
printf(
"%d \n"
,p.x);
// 指针类型
int
****** x = (
int
******)
1
;
int
******& ref = x;
ref = (
int
******)
2
;
printf(
"%d \n"
,x);
// 数组类型
int
arr[] = {
1
,
2
,
3
};
int
(&p)[
3
] = arr;
p[
0
] =
4
;
printf(
"%d \n"
,arr[
0
]);
如上是引用类型作用在各个类型下的例子,那么引用类型是如何实现的呢?其本质是什么?我们可以看下反汇编代码:
会发现这段反汇编和指针的反汇编一模一样的:
这时候我们暂时的出结论:引用类型就是指针。
在我vs2022里实验下:
但如果引用类型就是指针,为什么C++需要新创建一个引用类型的概念呢?它们之间必然存在着一些区别,我们可以从初始化、运算、赋值来看反汇编代码的区别:
我们可以很清晰的看见区别从运算到赋值都不一样,指针运算到赋值改变的是指针本身,而不是指针指向的那个地址,而引用则不一样其从运算到赋值改变的是所引用的变量,我们得出这几个结论:
-
引用必须赋初始值,且只能指向一个变量,从一而终(专一);
-
对引用赋值,是对其指向的变量赋值,而不是修改引用本身的值;
-
对引用做运算,就是对其指向的变量做运算,而不是对引用本身做运算;
-
引用类型就是一个弱化了的指针;个人理解:引用类型就是一个*p。
C++设计引用类型是因为指针类型很难驾驭,一旦用不好就回出问题,所以取长补短设计了引用类型。
那么引用类型在实际开发中的作用是什么呢?我们可以用在函数参数传递中:
#include <stdio.h>
void
Plus(
int
& i) {
i++;
return
;
}
void
main() {
int
i =
10
;
Plus(i);
printf(
"%d \n"
, i);
return
;
}
如上代码中Plus函数的参数是一个引用类型,当我们把变量i传递进去,i就会自增1,而实际上也就修改变量i本身的值;换一种说法就是,我们之前函数参数传递的是值,而这里传递的是变量的地址。
那么在构造类型中又是怎么样的呢?
#include <stdio.h> class Base { public: int x; int y; Base(int a, int b) { this->x = a; this->y = b; } }; void PrintByRef(Base& refb, Base* pb) { printf("%d %d \n", pb->x, pb->y); printf("%d %d \n", refb.x, refb.y); } void main() { Base b(1, 2); PrintByRef(b, &b); return; }
我们可以看见除了读取的表现形式不一样,实际上汇编代码是一模一样的;但是指针类型是可以重新赋值并运算的,而引用类型不可以。
当一个变量是int类型的,而我们引用类型却是一个其他类型的,会怎么样呢?
int
x =
10
;
Person& ref = (Person&)x;
这是可以编译的,但是没有实际意义,所以在使用引用的时候原来是什么类型就应该使用什么类型。
大家都知道,我们使用指针的时候是可以修改指针本身的,这会存在一定的风险,而C++中提供了引用类型,不可以修改引用本身,我们可以修改被引用的值,当我们不想其他人修改引用类型对应引用的值,可以使用const这个关键词,这种方式我们称之为常引用: