跳至主要內容

静态绑定和🍔动态绑定

张威大约 4 分钟c/c++多态

静态绑定和动态绑定

静态绑定

静态指的是编译时期

绑定即函数调用

#include <iostream>
#include <typeinfo>
using namespace std;

class Base
{
public:
	Base(int data = 10): ma(data){}
	void show()
	{
		cout << "Base::show()" << endl;
	}
	void show(int)
	{
		cout << "Base::show(int)" << endl;
	}
protected:
	int ma;
};

class Derive : public Base
{
public:
	Derive(int data = 20):Base(data),mb(data){}
	void show()
	{
		cout << "Derive::show()" << endl;
	}
private:
	int mb;
};

int main()
{
	Derive d(50);
	Base *pb = &d;
	pb->show();
	pb->show(10);
	
	cout << sizeof(Base) << endl;//4
	cout << sizeof(Derive) << endl;//8

	cout << typeid(pb).name() << endl;//class Base *	//该指针的类型,定义的是Base*,所以打印也是Base*
	cout << typeid(*pb).name() << endl;//class Base		//解引用,表示指针指向的类型

	return 0;
}

pb是基类类型, 调用的是派生类从基类继承来的方法

pb->show();//静态绑定  call Base::show(01612DAh)
pb->show(10);//静态绑定 call Base::show(01612B2h)

编译期间将高级源代码编译为汇编码,指定了call Base::show(01612DAh)call Base::show(01612B2h),即编译期间指定了函数的调用,这就是是静态绑定。

动态绑定

class Base
{
public:
	Base(int data = 10): ma(data){}
	virtual void show()//虚函数
	{
		cout << "Base::show()" << endl;
	}
	virtual void show(int)//虚函数
	{
		cout << "Base::show(int)" << endl;
	}
protected:
	int ma;
};
class Derive : public Base
{
public:
	Derive(int data = 20):Base(data),mb(data){}
	void show()
	{
		cout << "Derive::show()" << endl;
	}
private:
	int mb;
};

虚函数表里的“0”,表示的是虚函数指针vfptr在虚函数表里的偏移量

	Derive d(50);
	Base *pb = &d;
	pb->show();
	pb->show(10);

🍗🍗🍗pb依然是Base类型的指针,指向的是派生类对象,编译阶段编译器先去Base作用域看一下Base::show()

  • 若是普通函数,则进行静态绑定,call Base::show()
  • 若是虚函数,则进行动态绑定,
mov eax,dword ptr[pb] #将派生类对象的前4个字节(虚函数表的地址vfptr)放入eax寄存器
mov ecx,dword ptr[eax] #将vfptr存的地址(派生类虚函数表)的前4字节内存&Derive::show()地址放入ecx
call ecx #调用ecx,取虚函数地址

最终call 一个寄存器,只有在运行时候才知道寄存器的地址,找到哪个地址调用哪个函数,这就是动态绑定

sizeof变化的原因:

多了virtual,即虚函数会多vfptr指针,因此sizeof()大小也会变。

*pb的类型:typeid()

  • 如果Base没有虚函数,pb*Class Base;*pb识别的就是编译时期的类型, *pb就是Class Base
  • 如果Base有虚函数,pb*Class Base*pb识别的就是运行时期的类型RTTI类型,即 Class Derive

RTTI运行时的类型信息

存储的时候字符串类型,表示该虚函数表是由什么类类型产生的

RTTI应用:可以通过typeid()判断两个类是否属于同一个类

C++ RTTI介绍及使用示例(run-time type Identification)_rtti 使用-CSDN博客open in new window

vs 查看内存分布

  1. cmd找到源文件所在目录

  2. 输入下面命令

    cl -d1reportSingleClassLayout(输出对象内存布局信息,类的名字)
    

注意:在虚函数表中,派生类重写的函数的入口地址应该在基类的函数入口地址的下面,基类被重写的函数的入口地址应该是直接不用了,派生类的放在基类后

总结

  1. 静态指的是编译时期

  2. 动态是运行时期

  3. 绑定指的是函数调用

  4. 动态绑定一定是对虚函数的绑定

因此:

静态绑定会生成一个指令,call一个具体函数地址

动态绑定:若指针指向的是虚函数,则根据派生类前四个字节获取获取虚函数表指针,进一步获取虚函数表中虚函数地址,指令上是call一个寄存器,寄存器放的地址,只能运行时才能知道