从底层理解类

「C++ 40 周年」主题征文大赛(有机会与C++之父现场交流!) 10w+人浏览 140人参与

从底层理解类

反汇编设置

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

image-20251108094415821

image-20251108094458281

image-20251108094945531

函数调用约定

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函数。

image-20251108095019996

​ 继续跳转:

image-20251108095118562

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

image-20251108095221144

​ 当在Add函数中调用show类成员函数时,通过观察下面结果可知,同样使用了this指针。

int Add(int a, int b)
{
    show(); 
    return hp + mp + a + b; 
}

void show()
{

}

image-20251108095549587

总结:thiscall是C++的成员函数访问时定义的函数调用约定,其遵守下面的规则

  1. 寄存器ecx用于存储类的地址,即this指针
  2. 调用的参数从右向左入栈
  3. 堆栈有调用者负责恢复

类中静态成员

​ 对于类的非静态成员变量和函数是_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指针。可见,对于静态成员函数和静态成员变量,就相当于一个全局变量和全局函数

image-20251108100234171

image-20251108100327268

image-20251108100348073

​ 总结:对于类的静态成员函数调用遵循一下的规则:

  1. 调用的参数从右向左入栈
  2. 堆栈有调用者负责恢复,在静态成员调用完成之后再 增加esp指针的

类一定有构造函数吗?

​ 根据C++标准的规定是一定有构造函数,就算没有给定,编译器也会自动生成一个默认构造函数。但是在编译的时候,如果执行的是默认构造函数,即什么事情也不做,编译器可能会将这个默认构造函数优化掉,即不执行也就是没有构造函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值