C++逆向分析——构造函数和析构函数
构造函数与析构函数
构造函数
struct Student {
int
a;
int
b;
Student() {
printf(
"Look."
);
}
void
Init(
int
a,
int
b) {
this
->a = a;
this
->b = b;
}
};
如上代码中,我们发现了存在一个函数,这个函数没有返回类型并且与结构体名称一样,那这段函数在什么时候执行呢?
我们先不使用之前学习的方法去调用,直接创建一个对象,这时候会发现该函数就直接执行了:
这个函数,我们就称之为构造函数。==》在汇编看来,这个构造函数和任何函数没有区别!
它的汇编代码如下:
贴下我vs2022里的汇编代码:
struct Student { int a; int b; Student() { 01001770 55 push ebp 01001771 8B EC mov ebp,esp 01001773 81 EC CC 00 00 00 sub esp,0CCh 01001779 53 push ebx 0100177A 56 push esi 0100177B 57 push edi 0100177C 51 push ecx 0100177D 8D 7D F4 lea edi,[ebp-0Ch] 01001780 B9 03 00 00 00 mov ecx,3 01001785 B8 CC CC CC CC mov eax,0CCCCCCCCh 0100178A F3 AB rep stos dword ptr es:[edi] 0100178C 59 pop ecx 0100178D 89 4D F8 mov dword ptr [this],ecx 01001790 B9 08 C0 00 01 mov ecx,offset _F4F12A98_Conso@cpp (0100C008h) 01001795 E8 81 FB FF FF call @__CheckForDebuggerJustMyCode@4 (0100131Bh) printf("Look."); 0100179A 68 D0 7B 00 01 push offset string "Look." (01007BD0h) 0100179F E8 29 F9 FF FF call _printf (010010CDh) 010017A4 83 C4 04 add esp,4 } 010017A7 8B 45 F8 mov eax,dword ptr [this] 010017AA 5F pop edi 010017AB 5E pop esi 010017AC 5B pop ebx 010017AD 81 C4 CC 00 00 00 add esp,0CCh 010017B3 3B EC cmp ebp,esp 010017B5 E8 85 FA FF FF call __RTC_CheckEsp (0100123Fh) 010017BA 8B E5 mov esp,ebp 010017BC 5D pop ebp 010017BD C3 ret
如果我们想要在创建对象的时候,自定义初始化成员的值,就可以在析构函数上加上参数:
struct Student {
int
a;
int
b;
Student(
int
a,
int
b) {
this
->a = a;
this
->b = b;
}
void
Init(
int
a,
int
b) {
this
->a = a;
this
->b = b;
}
};
void
main() {
Student s(
1
,
2
);
return
;
}
创建对象的时候,在对象名后面加上括号传入即可;但是这样就会存在一个问题,我们不想初始化值的时候就没有办法创建这个类:
struct Student {
int
a;
int
b;
Student(
int
a,
int
b) {
this
->a = a;
this
->b = b;
}
void
Init(
int
a,
int
b) {
this
->a = a;
this
->b = b;
}
};
void
main() {
Student s;
return
;
}
编译直接出错:
这是因为编译器发现你没有传入参数,就会去寻找没有参数的构造函数,但是在这段代码中没有声明,所以需要声明一下:
#include <stdio.h>
struct Student {
int
a;
int
b;
Student() {
printf(
"Look."
);
}
Student(
int
a,
int
b) {
this
->a = a;
this
->b = b;
}
void
Init(
int
a,
int
b) {
this
->a = a;
this
->b = b;
}
};
void
main() {
Student s;
return
;
}
这样就没有任何问题了,你想传参就传,不想就不传。
我们总结一下其(构造函数)特点:
-
构造函数名称与类名一样
- 不能写返回类型(无返回值)==》严格说,vs底层反汇编是设置eax为this指针,汇编码为:mov eax,dword ptr [this]
-
创建对象时,则会自动调用执行,一般用于初始化
-
可以有多个构造函数(建议只有一个无参的),这种声明方式我们称之为重载(其他函数也可以)
-
编译器不要求必须提供构造函数
上面黄色部分的观点补充:
结合前面博客this指针的讲解,书中也给出了一个例子,如下:
析构函数
析构函数函数的语法跟构造函数很像,其区别就是:析构函数需要在函数名前面加一个波浪号、析构函数只能有一个、析构函数函数不可以写参数、构造函数是创建对象的时候执行,但是析构函数函数是在对象销毁前执行:
#include <stdio.h>
struct Student {
int
a;
int
b;
Student() {
printf(
"Look."
);
}
Student(
int
a,
int
b) {
this
->a = a;
this
->b = b;
}
~Student() {
printf(
"Look A."
);
}
void
Init(
int
a,
int
b) {
this
->a = a;
this
->b = b;
}
};
void
main() {
Student s;
return
;
}
析构函数函数是在对象销毁前执行,那么对象会在什么时候销毁呢?可以看下反汇编代码:
在我的vs2022编译代码里,看到的反汇编码:
Student s; 0012194F 8D 4D F0 lea ecx,[s] 00121952 E8 5F FA FF FF call Student::Student (01213B6h) return; 00121957 8D 4D F0 lea ecx,[s] 0012195A E8 61 FA FF FF call Student::~Student (01213C0h) } ~Student() { 00121FC0 55 push ebp 00121FC1 8B EC mov ebp,esp 00121FC3 81 EC CC 00 00 00 sub esp,0CCh 00121FC9 53 push ebx 00121FCA 56 push esi 00121FCB 57 push edi 00121FCC 51 push ecx 00121FCD 8D 7D F4 lea edi,[ebp-0Ch] 00121FD0 B9 03 00 00 00 mov ecx,3 00121FD5 B8 CC CC CC CC mov eax,0CCCCCCCCh 00121FDA F3 AB rep stos dword ptr es:[edi] 00121FDC 59 pop ecx 00121FDD 89 4D F8 mov dword ptr [this],ecx 00121FE0 B9 08 C0 12 00 mov ecx,offset _F4F12A98_Conso@cpp (012C008h) 00121FE5 E8 31 F3 FF FF call @__CheckForDebuggerJustMyCode@4 (012131Bh) printf("Look A."); 00121FEA 68 D8 7B 12 00 push offset string "Look A." (0127BD8h) 00121FEF E8 D9 F0 FF FF call _printf (01210CDh) 00121FF4 83 C4 04 add esp,4 }
注意,也通过ecx传递了一个this指针进去。
------
会发现在程序执行结束,也就是main函数的return之后会执行析构函数函数,但这句话实际上是不严谨的,因为我们的main函数是没有返回值的,也就是return不会有对应的汇编代码,当我们设置返回值再来看下反汇编代码:
#include <stdio.h>
struct Student {
int
a;
int
b;
Student() {
printf(
"Look."
);
}
Student(
int
a,
int
b) {
this
->a = a;
this
->b = b;
}
~Student() {
printf(
"Look A."
);
}
void
Init(
int
a,
int
b) {
this
->a = a;
this
->b = b;
}
};
int
main() {
Student s;
return
0
;
}
可以很清晰的看见,析构函数是在return返回之前执行的。
我们总结(析构函数)一下:
-
只能有一个,不支持重载
-
无返回值
-
无任何参数
-
主要用于清理工作
-
编译器不要求必须提供
-
当对象在main函数(堆栈)中创建,在return之前调用执行;当对象在全局变量区,则会在应用程序退出之前调用