从底层理解类
反汇编设置
对于visual studio中调试时,使用反汇编时,一定要关闭优化和安全检查,否则可能会影响我们观察汇编代码。



函数调用约定
1.__cdecl (C默认缺省函数调用约定)
- 参数是从右向左传递的,也是放在堆栈中。
- 堆栈平衡是由调用函数来执行的(函数具有可变参数时,调用完才知道使用字节数,才能清理)。 3、函数的前面会加一个前缀"“修饰,
2.__stdcall
- 参数是从右往左传递的,也是放在堆栈中。
- 函数的堆栈平衡操作是由被调用函数执行的。
3.__fastcall
- 参数是从右往左传递的,也是放在堆栈中。fastcall见名知其意,其特点就是快。fastcall函数调用约定表明了参数应该放在寄存器中,而不是在栈中,VC编译器采用调用约定传递参数时,最左边的两个不大于4个字节(DWORD)的参数分别放在ecx和edx寄存器。当寄存器用完的时候,其余参数仍然从右到左的顺序压入堆栈。像浮点值、远指针和int64类型总是通过堆栈来传递的。
4.__thiscall(不能直接修饰,C++默认缺省函数调用约定)
- 参数是从右往左传递的,也是放在堆栈中
- 函数的堆栈平衡操作对于参数确定的函数是由被调用函数执行的。函数的堆栈平衡操作对于不参数确定的函数是由调用函数执行的。
- _thiscall是C++成员函数的默认调用约定,__thiscall不是关键字,不能进行显示指定。参数是从右向左压栈,由被调用的函数清理堆栈。而且使用ecx寄存器来传递this指针。(注意:并不是所有的成员函数调用都是通过ecx来实现的,得看具体的编译器)
类的函数调用约定this call
对于类中的函数调用,都是会通过ecx寄存器传递this指针,若对于的类函数调用使用到类成员变量或者类成员函数,都会通过ecx保持的地址来访问类中的变量和函数。
代码示例:
#include <iostream>
using namespace std;
class T
{
int hp;
public:
int Add(int a, int b)
{
return hp + a + b;
}
};
int main()
{
T t1;
t1.Add(100, 200); // 此处打断点进行反汇编观察
return 0;
}
调试过程:
可以看到调用类成员函数时,将类的地址传递给了ecx寄存器,然后调用Add函数。

继续跳转:

可以观察到,首先将[eax] 数值(也就是hp)放入eax,然后使用ecx作为地址存储寄存器,然后将[exc + 4]的数值(也就是mp)加到eax,最后再加上a和b。

当在Add函数中调用show类成员函数时,通过观察下面结果可知,同样使用了this指针。
int Add(int a, int b)
{
show();
return hp + mp + a + b;
}
void show()
{
}

总结:thiscall是C++的成员函数访问时定义的函数调用约定,其遵守下面的规则
- 寄存器ecx用于存储类的地址,即this指针
- 调用的参数从右向左入栈
- 堆栈有调用者负责恢复
类中静态成员
对于类的非静态成员变量和函数是_thiscall约定,那么对于静态成员变量和函数呢?对于静态成员函数遵循cdecl约定 。
代码示例:
#include <iostream>
using namespace std;
class T
{
int hp;
int mp;
static int count;
public:
static int test(int a, int b)
{
return count + a;
}
int Add(int a, int b)
{
show();
return hp + mp + a + b;
}
void show()
{
}
};
int T::count = 0;
int main()
{
T t1;
//t1.Add(100, 200);
t1.test(100, 200);
return 0;
}
结果:
首先,第一步调用时并没有传入this指针。然后再静态函数中计算的时候,也没有使用this指针。可见,对于静态成员函数和静态成员变量,就相当于一个全局变量和全局函数。



总结:对于类的静态成员函数调用遵循一下的规则:
- 调用的参数从右向左入栈
- 堆栈有调用者负责恢复,在静态成员调用完成之后再 增加esp指针的
类一定有构造函数吗?
根据C++标准的规定是一定有构造函数,就算没有给定,编译器也会自动生成一个默认构造函数。但是在编译的时候,如果执行的是默认构造函数,即什么事情也不做,编译器可能会将这个默认构造函数优化掉,即不执行也就是没有构造函数。

被折叠的 条评论
为什么被折叠?



