从底层理解类
函数调用约定_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指针
类的静态成员函数
将上述代码中的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约定
类的构造函数
通过上面的汇编代码,我们可以看到,每次创建了对象后,都没有调用构造函数,我们知道,每个类都有构造,如果没有,编译器也会给我们添加一个,那这里为什么没有呢,这是因为默认的构造函数只是一个空实现,所以被编译器优化删除掉了。原则上来说,每个类都有构造函数。