当前位置:网站首页>类的底层机制
类的底层机制
2022-08-05 04:58:00 【小鸡岛~】
提出问题:
- 为什么类函数调用约定必须是this call
- 为什么静态成员函数没有this指针
- 为什么类的静态成员函数不能访问类的非静态成员函数
- 类真的有构造函数吗
C++代码
class T {
int hp;
public:
int Add(int a, int b) {
return hp + a + b;
}
};
int main(int count, char** args)
{
T t1;
t1.Add(100, 200);
}
汇编代码
00241034 push 0C8h
00241039 push 64h
0024103B lea ecx,[ebp-4]
0024103E call 00241000
00241000 55 push ebp
00241001 8B EC mov ebp,esp
00241003 51 push ecx
00241004 89 4D FC mov dword ptr [ebp-4],ecx //ecx值变成了局部变量
00241007 8B 45 FC mov eax,dword ptr [ebp-4]
0024100A 8B 00 mov eax,dword ptr [eax] //this->hp
0024100C 03 45 08 add eax,dword ptr [ebp+8]
0024100F 03 45 0C add eax,dword ptr [ebp+0Ch]
00241012 8B E5 mov esp,ebp
00241014 5D pop ebp
00241015 C2 08 00 ret 8
设计实验:
当函数有使用this->hp时,ecx都会被用来存储类的首地址
当函数没有有使用this->hp时,ecx也会被用来存储类的首地址
发现规律:当调用类的成员函数的时候,ecx会存储类的首地址,然后会被当作局部变量来使用
得出结论
_thiscall 是C++中 类的成员函数访问时 定义的函数调用约定
(1) 寄存器ecx用来存放类的指针
(2) 参数由右到左入栈
(3) 堆栈由 被调用者 负责恢复
类的非静态成员函数都可以使用this指针,this指针本质上来讲就是把对象中的指针通过寄存器ecx来传入成员函数的,因此类中的成员函数访问其成员变量,都是通过指针+偏移的形式来访问的,不管是否明确使用this指针
设计实验
C++代码
class T {
inline static int count;
public:
static int GetCount(int a, int b) {
count++;
return 2;
}
};
int main(int count, char** args)
{
T t1;
t1.GetCount(2, 3);
汇编代码
00D31023 6A 03 push 3
00D31025 6A 02 push 2
00D31027 E8 D4 FF FF FF call T::GetCount (0D31000h)
00D3102C 83 C4 08 add esp,8**
count++;
00A21003 A1 28 31 A2 00 mov eax,dword ptr ds:[00A23128h]
00A21008 83 C0 01 add eax,1
00A2100B A3 28 31 A2 00 mov dword ptr ds:[00A23128h],eax
得出结论:
类的静态成员函数,本质上是采用的_cdecl约定
(1)参数由右到左入栈
(2) 由[调用者]恢复堆栈平衡
答:因为类的静态成员函数本质上就是一个普通函数,所以根本没有传递对象的指针,因此也就不能访问其成员变量;
而类的静态成员变量本质上相当于一个全局变量,有固定的内存地址,与类对象并无关系,所以类的静态成员可以在类没有实例的情况下通过[类::静态成员]这样的形式访问
设计实验:
int hp{
1};
发现规律:
- 当int hp{1}时,反汇编代码调用了构造函数,调用目的是让给hp赋值
- 当int hp 时,反汇编代码没有调用构造函数
得出结论:
类T大部分的时候是没有构造函数的,这是因为C++标准委员会要求每一个类都有默认的构造函数。
但是一个空的构造函数实际上没有任何的意义,所以某些情况下编译器会删除掉没有意义的构造函数,这是编译器优化的结果。
原则上来讲,每一个类都有默认构造函数。
提出问题:
- 为什么AIM的大小为8个字节?
- p->Die()是如何保证调用的是WOLF里面的Die()
class AIM {
public :
int HP;
virtual void Eat() {
std::cout << "AIM" << std::endl;
}
virtual void Die() {
std::cout << "AIM-DIE" << std::endl;
}
};
class WOLF :public AIM{
public:
virtual void Eat() {
std::cout << "WOLF" << std::endl;
}
virtual void Die() {
std::cout << "WOLF-DIE" << std::endl;
}
void Sound() {
std::cout << "aoaoaoaoaoaoao!!!!" << std::endl;
}
};
int main(int count, char** args)
{
AIM* p = new WOLF();
p->Die();
std::cout << sizeof(AIM) << std::endl;
}
猜测+实证:AIM类里应该存储了一个四字节的地址,通过它可以去访问虚函数的地址。通过下面的打印语句可知这个四字节放在了AIM类的首地址
std::cout << p << " " << &p << std::endl;
问题2猜测:调用p->Die()函数时会传递存储了WOLF类的虚函数地址的指针进去,然后计算出对应的虚函数地址
实证:逆向分析
[ebp-4]里面的地址→eax
eax里面的内存地址的前四个字节→edx
[edx+4]这个内存地址的前四个字节→ eax
p->Die();
00D11A5D 8B 45 FC mov eax,dword ptr [p]
00D11A60 8B 10 mov edx,dword ptr [eax]
00D11A62 8B 4D FC mov ecx,dword ptr [p]
00D11A65 8B 42 04 mov eax,dword ptr [edx+4]
00D11A68 FF D0 call eax
发现规律:
- 虚函数的地址会被放在一张虚表里
- 这个虚表的地址会被放在类的首地址,当我们通过指针调用虚函数的时候会通过虚表计算出该地址,然后执行

直接构建
std::cout << std::hex << "vtable地址:" << pTable[0] << std::endl;
int* func = (int*)pTable[0];
std::cout << std::hex << "Eat地址:" << func[0] << std::endl;
std::cout << std::hex << "Die地址:" << func[1] << std::endl;
unsigned* eat = (unsigned*)func[1];
__asm {
call eat
}
得出结论
(1)同一个类的多个实例都指向同一个虚函数表
(2)只有通过指针访问虚函数才会调用虚函数表
边栏推荐
- Excel Paint
- Learning and finishing of probability theory 8: Geometric and hypergeometric distributions
- 【cesium】3D Tileset 模型加载并与模型树关联
- 【学生毕业设计】基于web学生信息管理系统网站的设计与实现(13个页面)
- 数字孪生技术在电力系统中的应用现状
- C#关于set()和get()方法的理解及使用
- dedecms织梦tag标签不支持大写字母修复
- C语言-大白话理解原码,反码和补码
- Paparazzi: Surface Editing by way of Multi-View Image Processing
- How to deal with DNS hijacking?
猜你喜欢

Please write the SparkSQL statement

Feature preprocessing

Day019 Method overriding and introduction of related classes

Mysql的redo log详解

In the WebView page of the UI automation test App, the processing method when the search bar has no search button

flink读取mongodb数据源

【转】什么是etcd
![[8.1] Code Source - [The Second Largest Number Sum] [Stone Game III] [Balanced Binary Tree]](/img/f3/0d92e22a424206241f4e1640f1bf6b.png)
[8.1] Code Source - [The Second Largest Number Sum] [Stone Game III] [Balanced Binary Tree]

数字孪生技术在电力系统中的应用现状

8.04 Day35-----MVC three-tier architecture
随机推荐
Day019 方法重写与相关类的介绍
How does the Flutter TapGestureRecognizer work
1068 Find More Coins
Excel Paint
关于sklearn库的安装
Bytebuffer put flip compact clear method demonstration
How can Flutter parent and child components receive click events
1007 Climb Stairs (greedy | C thinking)
In the WebView page of the UI automation test App, the processing method when the search bar has no search button
C语言-大白话理解原码,反码和补码
mysql数据库表什么字段类型的存储长度最大?
Day14 jenkins deployment
【cesium】3D Tileset 模型加载并与模型树关联
淘宝账号如何快速提升到更高等级
for..in和for..of的区别
flink读取mongodb数据源
Cron(Crontab)--use/tutorial/example
【cesium】加载并定位 3D Tileset
JeeSite New Report
Shell(4)条件控制语句