> 文章列表 > 从底层理解类

从底层理解类

从底层理解类

函数调用约定_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约定

类的构造函数

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