当前位置:网站首页>类的底层机制
类的底层机制
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)只有通过指针访问虚函数才会调用虚函数表
边栏推荐
- Mysql's redo log detailed explanation
- dedecms dream weaving tag tag does not support capital letters fix
- Application status of digital twin technology in power system
- 狗仔队:表面编辑多视点图像处理
- Mysql的undo log详解
- 【cesium】3D Tileset 模型加载并与模型树关联
- 虚证、实证如何鉴别?
- How can Flutter parent and child components receive click events
- [BSidesCF 2019] Kookie
- Detailed explanation of each module of ansible
猜你喜欢
Paparazzi: Surface Editing by way of Multi-View Image Processing
大学物理---质点运动学
upload上传图片到腾讯云,如何上传图片
Flutter learning 5-integration-packaging-publish
University Physics---Particle Kinematics
[8.1] Code Source - [The Second Largest Number Sum] [Stone Game III] [Balanced Binary Tree]
dedecms后台生成提示读取频道信息失败的解决方法
C+ +核心编程
flink读取mongodb数据源
Analyses the mainstream across technology solutions
随机推荐
Bytebuffer put flip compact clear method demonstration
【微信小程序】WXML模板语法-条件渲染
Four-digit display header design
[8.3] Code Source - [meow ~ meow ~ meow~] [tree] [and]
Flutter学习三-Flutter基本结构和原理
[cesium] 3D Tileset model is loaded and associated with the model tree
大学物理---质点运动学
Flutter TapGestureRecognizer 如何工作
仪表板展示 | DataEase看中国:数据呈现中国资本市场
There are a lot of 4T hard drives remaining, prompting "No space left on device" insufficient disk space
LAB 信号量实现细节
Flutter learning 5-integration-packaging-publish
Flutter学习5-集成-打包-发布
[Nine Lectures on Backpacks - 01 Backpack Problems]
ESP32 485光照度
[8.2] Code Source - [Currency System] [Coins] [New Year's Questions (Data Enhanced Edition)] [Three Stages]
Redis - 13. Development Specifications
Mysql的undo log详解
MySQL基础(一)---基础认知及操作
【cesium】3D Tileset 模型加载并与模型树关联