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