C++ 编译调试看多态实现原理

  • Post author:
  • Post category:其他


C++多态实现原理探究

多态:是子类对象的地址赋值给父类指针,接收同一消息运行时产生不同方式的响应;

//有虚函数的类A
class A
{
	virtual void fun()
	{
	};
};
//有虚函数的类C
class C
{
	virtual void test()
	{
	};
};
//继承A和C的类B  多继承
class B: public A, public C
{

};
//没有虚函数的类D
class D
{
	
};
//有函数,但不是虚函数
class E
{
	void fun()
	{

	};
};
//继承有虚函数的类F
class F: public C
{

};
//有成员变量的G
class G
{
	int a;
};
int _tmain(int argc, _TCHAR* argv[])
{
	A a;
	B b;
	C c;
	D d;
        F  f;
        cout<<"sizeof(A)"<<sizeof(A)<<endl;//4 有虚函数会产生一个虚函数表,虚函数表中会有一个虚函数指针 占4个字节
	cout<<"sizeof(C)"<<sizeof(C)<<endl;//4 同A
	cout<<"sizeof(B)"<<sizeof(B)<<endl; //8多继承会有多个虚函数表 包含A和C的虚函数表 总共8个字节
	cout<<"sizeof(D)"<<sizeof(D)<<endl;//1 没有虚函数,操作系统分配一个字节的内存
	cout<<"sizeof(E)"<<sizeof(E)<<endl;//1 有一个函数,函数不需要内存
	cout<<"sizeof(F)"<<sizeof(F)<<endl;//4 继承一个类C   单继承 只有一个虚函数表
	cout<<"sizeof(G)"<<sizeof(G)<<endl;//4 有个整型成员变量 占4个字节
	system("pause");
	return 0;
}

输出结果:

输出结果可以看出,普通的类,不存在虚函数内存为1,当含有虚函数后,就会存在虚函数表,虚函数表中就有虚函数指针占用4个字节的长度,单继承只有一个虚函数表,多继承会有多个虚函数表。

调试可以看出内存结构模型:


从上面内存可以看出没有使用虚函数的类D没有虚函数表和虚函数指针;而继承了类C的F 具有一个虚函数指针,同时继承A和C的类B同时具有A和C的虚函数表和指针;那么派生类自己也存在虚函数时是怎么样的呢?

class H:public A
{
        //重写父类虚函数
        virtual void fun()
	{
	};
        //自己的虚函数
	virtual void testfun()
	{

	};
};

难道说派生类中的虚函数相当于普通函数吗?派生类中定义的非基类的虚函数,并没有产生新的虚函数表,也没有在基类的虚函数表中存在。

class A
{
public:
	virtual void fun()
	{
		printf("%s\n", __FUNCTION__);
	};
	virtual void myfun()
	{
		printf("%s\n", __FUNCTION__);
	};
};

class H :public A
{
public:
	virtual void fun()
	{
		printf("%s\n", __FUNCTION__);
	};
	virtual void testfun()
	{
		printf("%s\n", __FUNCTION__);
	};

	void testfun1()
	{
		printf("%s\n", __FUNCTION__);
	};
};

int _tmain(int argc, char* argv[])
{
	H h;

	// 我是x32编译的,如果是x64程序就把 unsigned int 换成unsigned long long
     unsigned int vptr = *((unsigned int *)(&h)); 
     printf("%p\n", vptr);
     unsigned int vfun1 = *((unsigned int *)(vptr));
     printf("%p\n", vfun1);
     unsigned int vfun2 = *((unsigned int *)(vptr) + 1);
     printf("%p\n", vfun2);
     unsigned int vfun3 = *((unsigned int *)(vptr) + 2);
     printf("%p\n", vfun3);
     unsigned int vfun4 = *((unsigned int *)(vptr) + 3);
     printf("%p\n", vfun4);

typedef void(*Fun)(void);Fun pfun = (Fun)vfun1; //输出 H::fun(); pfun();pfun = (Fun)vfun2; //输出 A::myfun(); pfun();pfun = (Fun)vfun3; //输出 H::testfun(); pfun();system(“pause”);return 0;}

003D7B00 属于虚函数指针起始地址

003D12B7 H::fun地址

003D12C6 A::fun地址

003D12B2 H::testfun地址 说明派生类的虚函数也是保存在那一个虚函数表,只是编译器上没有显示出来而已

(个人观点,如果不对,请指正)

00000000  NULL 本以为这个位置是普通函数的地址,但是普通函数是不会保存在虚函数表中的

派生类的虚函数和普通函数还是有区别的。编译器并不是按照同样的方式处理的。

调试看多态:

class Base
{
public:
	virtual void Name()
	{
		cout<<"Base:: Name()"<<endl;
	};

	virtual void Age()
	{
		cout<<"Base :: Age()"<<endl;
	};

	virtual void Sex()
	{
		cout<<"Base :: Sex()"<<endl;
	};
};


class Derived1 : public Base
{
	virtual void Name()
	{
		cout<<"Derived1:: Name()"<<endl;
	};

	virtual void Age()
	{
		cout<<"Derived1 :: Age()"<<endl;
	};

	virtual void Sex()
	{
		cout<<"Derived1 :: Sex()"<<endl;
	};
};

class Derived2 : public Base
{
	virtual void Name()
	{
		cout<<"Derived2:: Name()"<<endl;
	};

	virtual void Age()
	{
		cout<<"Derived2 :: Age()"<<endl;
	};

	virtual void Sex()
	{
		cout<<"Derived2 :: Sex()"<<endl;
	};
};

class Derived3 : public Base
{
	
};

int _tmain(int argc, _TCHAR* argv[])
{
    Derived1 d1;
	Derived2 d2;
	Derived3 d3;
	Base *     base;
	Base    base1;
	base = &d1;
	base->Name();

	base = &d2;
	base->Name();

 	system("pause");
	return 0;
}

从上面看出,基类vfptr指针地址00f07958 派生类 d1 :vfptr指针 00f078b8 ;d2: vfptr指针00f0785c;


从内存上看,如果单纯的基类,虚函数表中的地址是基类函数地址;00f07958  vfptr指针指向到了base1基类的地址

但当基类指针赋值之类对象地址后地址 00f00785c vfptr 指向派生类Derived1类的vfptr指针,虚函数表被替换为Derived1的虚函数表。在编译的时候地址是不确定的,只有在运行时,给父类指针赋值后,指向才会改变,才知道运行时调用那一个函数;

当你将其赋值为另外的子类对象时,相应的地址也会随之改变,可以看到赋值改变后,vfptr改变未00f078b8,为第二个派生类的虚函数指针地址;

输出结果:

我认为多态的原理,就是如果一个类中含有虚函数,那么这个类在实例化的时候就会产生一个虚函数表vftable,虚函数表中有一个虚函数指针vfptr,虚函数在虚函数表中的位置是按照声明时候的顺序确定的;在创建一个指向基类对象指针,这个时候这个指针是什么也没有的,指向NULL, 当你给他赋值某一个子类对象的时候,这个基类指针的vftable就会被替换为赋值后的那个子类的vftable虚函数表,这时候,vfptr指针也指向那个子类的虚函数地址(我认为这个过程叫动态绑定);只时候通过基类指针就能找到派生类函数的地址,运行的时候就不会调用到基类的函数,因为虚函数表已经被替换掉了,通过vfptr指针只能找到派生类的函数地址。



版权声明:本文为QWERDF10010原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。