C++逆向分析——构造函数和析构函数

构造函数与析构函数

构造函数

struct Student {
int a;
int b;
 
Student() {
printf("Look.");
}
 
void Init(int a, int b) {
this->a = a;
this->b = b;
}
 
};

如上代码中,我们发现了存在一个函数,这个函数没有返回类型并且与结构体名称一样,那这段函数在什么时候执行呢?

我们先不使用之前学习的方法去调用,直接创建一个对象,这时候会发现该函数就直接执行了:

images/download/attachments/12714553/image2021-3-31_17-4-32.png

这个函数,我们就称之为构造函数。==》在汇编看来,这个构造函数和任何函数没有区别!

它的汇编代码如下:

images/download/attachments/12714553/image2021-3-31_17-30-51.png

 

贴下我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;
}

编译直接出错:

images/download/attachments/12714553/image2021-4-1_15-42-56.png

这是因为编译器发现你没有传入参数,就会去寻找没有参数的构造函数,但是在这段代码中没有声明,所以需要声明一下:

#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;
}

这样就没有任何问题了,你想传参就传,不想就不传。

我们总结一下其(构造函数)特点

  1. 构造函数名称与类名一样

  2. 不能写返回类型(无返回值)==》严格说,vs底层反汇编是设置eax为this指针,汇编码为:mov         eax,dword ptr [this]
  3. 创建对象时,则会自动调用执行,一般用于初始化

  4. 可以有多个构造函数(建议只有一个无参的),这种声明方式我们称之为重载(其他函数也可以)

  5. 编译器不要求必须提供构造函数

 

上面黄色部分的观点补充:

结合前面博客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;
}

析构函数函数是在对象销毁前执行,那么对象会在什么时候销毁呢?可以看下反汇编代码:

images/download/attachments/12714553/image2021-4-1_16-9-20.png

在我的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;
}

images/download/attachments/12714553/image2021-4-1_16-12-49.png

可以很清晰的看见,析构函数是在return返回之前执行的。

我们总结(析构函数)一下:

  1. 只能有一个,不支持重载

  2. 无返回值

  3. 无任何参数

  4. 主要用于清理工作

  5. 编译器不要求必须提供

  6. 当对象在main函数(堆栈)中创建,在return之前调用执行;当对象在全局变量区,则会在应用程序退出之前调用

posted @ 2023-04-09 10:44  bonelee  阅读(88)  评论(0编辑  收藏  举报