从底层理解类

函数调用约定_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 Role
     4: {
     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肯定是调用成员方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值