从底层理解类

news/2024/11/24 7:38:32/

函数调用约定_thiscall

thiscall是对象调用类成员函数时的约定

class Role
{
public:int hp;int mp;int add(int a, int b){return hp + mp+ a+ b;}
};
int main()
{Role r;r.add(100, 200);
}

查看汇编代码

    15:     Role r;16:     r.add(100, 200);
00F91B08  push        0C8h  
00F91B0D  push        64h  
00F91B0F  lea         ecx,[r]  
00F91B12  call        Role::add (0F9125Dh) 

 这里参数从右往左传递,通过lea,将r传递给了ecx,这里ecx表示了对象r的地址,即hp的地址,勾选显示符号位,发现r的值为ebp-0Ch,然后进入了函数

知识扩展*:lea指令可以用来将一个内存地址直接赋给目的操作数,
例如:lea eax, [ebx+8] 就是将 ebx+8 这个值直接赋给 eax,而不是把 ebx+8 处的内存地址里的数据赋给 eax。

mov 指令则恰恰相反,dword 双字 就是四个字节 ptr pointer缩写 即指针 []里的数据是一个地址值,这个地址指向一个双字型数据  

例如mov eax, dword ptr [12345678] 把内存地址12345678中的双字型(32位)数据赋给eax

    class Role4: {5: public:6:     int hp;7:     int mp;8:     int add(int a, int b)9:     {
00F91650  push        ebp  
00F91651  mov         ebp,esp  
00F91653  push        ecx  
00F91654  mov         dword ptr [this],ecx  10:         return hp + mp+ a+ b;
00F91657  mov         eax,dword ptr [this]  
00F9165A  mov         eax,dword ptr [eax]  
00F9165C  mov         ecx,dword ptr [this]  
00F9165F  add         eax,dword ptr [ecx+4]  
00F91662  add         eax,dword ptr [a]  
00F91665  add         eax,dword ptr [b]  11:     }
00F91668  mov         esp,ebp  
00F9166A  pop         ebp  
00F9166B  ret         8  

看第10行的代码,先将ecx放入this指向的值,将this指针里面的值给eax,即eax=ecx,又将eax里面的值给了eax,即eax=*ecx,又将this指针里面的值给了ecx,即ecx=ecx,然后add ecx+4,eax,就是将dword ptr[ecx]+dword ptr[ecx+4],然后add a,add b,说明ecx+4就是mp的地址,ecx就是hp的地址,最后通过ret恢复栈平衡

结论

寄存器ecx存放类的指针,参数从右往左传递,堆栈由被调用者恢复平衡

this指针就是把对象的指针通过ecx传入成员函数,成员函数访问成员变量时,通过this+4即指针偏移来实现,不管源代码中有没有使用this指针

类的自定义函数约定

    int _cdecl add(int a, int b){return hp + mp+ a+ b;}
    Role r;r.add(100, 200);
00041B08  push        0C8h  
00041B0D  push        64h  
00041B0F  lea         eax,[r]  
00041B12  push        eax  
00041B13  call        Role::add (041267h)  
00041B18  add         esp,0Ch  

我们可以自定义函数约定,比如在函数名前加上_cdecl,看下它的汇编代码,push了三个参数,但是我们只传递了两个参数,这就是cdecl在类里面的表现,最后add esp 12,自己恢复栈平衡

总结:我们可以自定义类的函数约定,不一定要用ecx来传递this指针,有可能用eax

类的静态成员函数

将上述代码中的add函数修改成static函数

    15:     Role r;16:     r.add(100, 200);
00451B08  push        0C8h  
00451B0D  push        64h  
00451B0F  call        00451262  
00451B14  add         esp,8  

结论

从这里可以看到,add函数并没有传递this指针 ,这就是为什么静态成员函数不能使用this指针,因为它根本没有传递this指针,静态成员函数本质上就是个普通的函数,只不过作用域属于类里面

参数从右往左传递,因为add esp+8,所以由自己恢复栈平衡,静态成员函数本质上是使用cdecl约定

类的构造函数

通过上面的汇编代码,我们可以看到,每次创建了对象后,都没有调用构造函数,我们知道,每个类都有构造,如果没有,编译器也会给我们添加一个,那这里为什么没有呢,这是因为默认的构造函数只是一个空实现,所以被编译器优化删除掉了。原则上来说,每个类都有构造函数。

代码实战

class Role;
class medicine
{
private:int addhp;
public:medicine(int _addhp){addhp = _addhp;};
};class Role
{
private:int hp;int maxhp;
public:Role(int _hp ){hp = _hp;maxhp = 1000;}int tmedicine(medicine m){hp += m.addhp;hp = (hp + m.addhp) > maxhp ? maxhp : hp;return 0;}int showhp(){return hp ;}};int main()
{Role r1(100);while (true){int x{};std::cin >> x;medicine honey(x);r1.tmedicine(honey);std::cout << r1.showhp();system("pause");}
}

注意,这里将代码跑起来后,还要再命令行里输入x的值,才能看到汇编代码

看下汇编代码

r1.tmedicine(honey);
007D4F96  mov         eax,dword ptr [ebp-24h]  
007D4F99  push        eax  
007D4F9A  lea         ecx,[r1]  
007D4F9D  call        Role::tmedicine (07D1019h)  

这里传递的eax,是一个值,因为我们的tmedicine时候用的是值传递,如果采用引用传递,这里应该就是用lea传递一个地址,如果看到第三行的汇编代码,lea ecx ,就要立马想到这里传递的是类的指针,下面的call肯定是调用成员方法


http://www.ppmy.cn/news/47399.html

相关文章

[网络安全]第三次作业

目录 1. 什么是IDS&#xff1f; 2. IDS和防火墙有什么不同&#xff1f; 3. IDS工作原理&#xff1f; 4. IDS的主要检测方法有哪些详细说明&#xff1f; 5. IDS的部署方式有哪些&#xff1f; 6. IDS的签名是什么意思&#xff1f;签名过滤器有什么作用&#xff1f;例外签名…

记录解决Maven依赖冲突导致的NoSuchMethodError问题的过程

摘要 本文记录了解决 Maven 依赖冲突导致的 NoSuchMethodError 问题的过程。问题出现的原因是多个库包含了 Jackson 库&#xff0c;导致 Jackson 序列化与反序列化时出现 NoSuchMethodError 异常。通过查看依赖树&#xff0c;排除冲突库的方法&#xff0c;最终成功解决了该问题…

查询练习:YEAR 与 NOW 函数

查询 student 表中每个学生的姓名和年龄。 -- 使用函数 YEAR(NOW()) 计算出当前年份&#xff0c;减去出生年份后得出年龄。 SELECT name, YEAR(NOW()) - YEAR(birthday) as age FROM student; ----------------- | name | age | ----------------- | 曾华 | 42 |…

垃圾收集器面试总结(一)

垃圾收集器 Serial 收集器&#xff08;GC日志标识&#xff1a;DefNew&#xff09; Serial&#xff08;串行&#xff09;收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。 它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾…

Python OpenCV3 计算机视觉秘籍:6~9

原文&#xff1a;OpenCV 3 Computer Vision with Python Cookbook 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【ApacheCN 计算机视觉 译文集】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 当别人说你没有底线的时候&…

如何使用Socks5代理来保护个人隐私和网络安全

在当今互联网的环境中&#xff0c;我们经常需要通过代理服务器来保护我们的网络安全和隐私。Socks5代理是一种广泛使用的代理协议&#xff0c;它提供了许多安全和隐私保护的功能。在本文中&#xff0c;我们将探讨Socks5代理和网络安全的关系&#xff0c;并介绍如何使用Socks5代…

wps - 使用宏来为每行标记序号

在WPS中遇到需要加序号的虽然可以用项目符符号来标注# 但或许用宏更厉害一些 WPS 的宏是一种自动化工具&#xff0c;可以通过编写宏代码来自动执行一些操作&#xff0c;例如创建和编辑文档、打印、格式化等。 WPS 宏的编写可以使用 VBA&#xff08;Visual Basic for Applicat…

使用全球融合CDN的10大优势

根据预估&#xff0c;今年的全球内容交付网络&#xff08;CDN&#xff09;市场预计将达到424亿美元。而由于移动应用程序的激增和人工智能尤其是ChatGPT等相关领域的快速发展将进一步带来CDN市场的快速增长&#xff0c;可以说全球CDN的黄金时代才刚开始。 融合CDN和多CDN战略是…