首页 > 编程知识 正文

虚函数表存在哪里,虚表和虚表指针

时间:2023-05-03 22:18:58 阅读:35103 作者:3466

1 .虚函数表指针的位置分析一个类中有虚函数时,生成虚函数表

生成此类的对象时,此对象生成指向虚数函数表开始位置的指针。 该指针是虚数函数指针(vptr ),包含类似于此对象的成员变量。 这占字节数。 例如,在vs2017中占4字节,在Linux中占8字节

vptr在对象内存中的位置取决于编译器

分析虚函数表指针的位置分析的代码:

# includeiostreamusingnamespacestd; class A{public:int i; //vs2017:4字节Linux下g中: 4字节virtual void testfunc () }//虚函数、vptr vs2017:4字节Linux下g中: 8字节); int main () {A a_obj; inta_obj_len=sizeof(a_obj ); intI_len=sizeof(a_obj.I ); cout 'a_obj_len=' a_obj_len endl; cout 'i_len=' i_len endl; //4字节char * P1=reinterpret _ cast char * (a_obj )//类型转换,硬转a _ obj,这是对象aobj的起始地址。 char * p2=reinterpret _ cast char * ((a _ obj.I ) ); if(p1==p2 ) /说明aobj.i和aobj的位置相同,说明I在对象aobj存储器布局的上边。 虚函数表指针vptr是以下{cout虚函数表指针位于对象存储器末尾的' endl; }else{cout虚函数表指针是对象存储器的开头' endl; }返回1; } VS2017上的执行结果:

在Linux上执行g编译的结果:

注意:在Linux上,虚数表指针为8字节,int为4字节。 程序的执行结果为16字节的是对象内存总大小必须为最长变量的整数倍

上述结果显示,在VS和Linux的g编译器中,虚函数表指针位于对象存储器的开头

大致如图:所示

2 .继承关系手动调用虚函数# includeiostreamusingnamespacestd; //父类class base (public : virtual voidf ) { cout 'Base:f ) ' endl; }virtual void g () ({ cout 'Base:g ) ) ' endl; }virtual void h () ({ cout 'Base:h ) ) ' endl; }; //子类class derive :公共基(virtualvoidg ) ) { cout ' derive :3360 g } ' endl; (//虚函数g ) )//int r; (; int main (()//输出两个类的内存大小coutsizeof ) ) base ) endl; 计数大小(derive ) endl; //从派生类对象转换为与其对应的Derive *d=new Derive (; //派生类的指针。 long*PVPtr=(long* ) d; //指向对象的指针d变为long *类型。 long*vptr=(long* ) ) *pvptr ); //(*pvptr )表示pvptr指向的对象,即Derive本身。 Derive对象为4字节,//表示虚函数表的指针地址。 //在此,函数定义中只有虚函数,因此Derive对象存储器的内容包括虚函数表指针、//long *vptr=(long * ) (pvptr[0] ); //例如,如果其他成员占用内存,则必须使用指针索引访问第一个大小的值。 ()其中内存是字节对齐的,因此指针索引一次访问最大成员长度值。 //首先访问的原因是虚拟函数表指针在VS和Linux下的位置位于对象内存的开头。 (cout ) ),输出每次Linux //函数调用的内存for (inti=0; i=5; I ) )//循环5次{printf(vptr[%d]=0x:%p(n ),I,vptr[i] ); //使用虚函数表指针访问和调用对应的虚函数

typedef void(*Func)(void); //定义一个函数指针类型Func f = (Func)vptr[0]; //f就是函数指针变量。 vptr[0]是指向第一个虚函数的。Func g = (Func)vptr[1];Func h = (Func)vptr[2]; f();g();h(); cout << "---------Base-----------" << endl;//同上Base *dpar = new Base();long *pvptrpar = (long *)dpar;long *vptrpar = (long *)(*pvptrpar); for (int i = 0; i <= 4; i++) //循环5次;{printf("vptr Base[%d] = 0x:%pn", i, vptrpar[i]);} Func fpar = (Func)vptrpar[0];Func gpar = (Func)vptrpar[1];Func hpar = (Func)vptrpar[2]; fpar();gpar();hpar(); return 1;}

运行结果:

解释:

1)两个4是虚函数表指针的大小

2)接下来都是通过虚函数表指针访问相应虚函数存储地址和调用虚函数的结果

具体一些其他解释,见代码中注释

 

图示理解:

 

问题:子类覆盖g()之后,原来父类中的虚函数还在吗?

个人理解:

每个类有自己的虚函数表,

父类的虚函数表中有f(),g(),h()
而子类的虚函数表为f(), h() 重写的g(),所以在子类的虚函数表中是没有父类的g()的

从上述结果可以发现子类和父类的虚函数表中未被子类覆盖的函数表项指向的内存地址相同,而被覆盖的函数表项指向的内存不同,说明这种覆盖是在内存上新写了子类函数的代码,而不是刷新内存中对应的父类函数代码,所以原来父类中的虚函数的定义还在内存中

只是它的调用需要去父类调用g(),就需要到父类的虚函数表中也就是存储该类的内存块中去找到g(),所以在前面加父类作用域,也就是到父类的内存地址域中去找

3.虚函数表分析

(1)一个类只有包含虚函数才会存在虚函数表,同属于一个类的对象共享虚函数表,但是有各自的vptr(虚函数表指针),当然所指向的地址(虚函数表首地址)相同。

(2)父类中有虚函数就等于子类中有虚函数。也就是,父类中有虚函数表,则子类中肯定有虚函数表。

只要在父类中是虚函数,那么子类中即便不写virtual,也依旧是虚函数。不管是父类还是子类,都只会有一个虚函数表

问题:子类中是否可能会有多个虚函数表呢?

(3)如果子类中完全没有新的虚函数,则我们可以认为子类的虚函数表和父类的虚函数表内容相同。但仅仅是内容相同,这两个虚函数表在内存中处于不同位置,换句话来说,这是内容相同的两张表。虚函数表中每一项,保存着一个虚函数的首地址,但如果子类的虚函数表某项和父类的虚函数表某项代表同一个函数(这表示子类没有覆盖父类的虚函数),则该表项所执行的该函数的地址应该相同。

(4)超出虚函数表部分的内容不可知,即没有意义

代码分析:

#include <iostream>using namespace std; //父类class Base{public:virtual void f() { cout << "Base::f()" << endl; }virtual void g() { cout << "Base::g()" << endl; }virtual void h() { cout << "Base::h()" << endl; }};class Derive :public Base{virtual void g() { cout << "Derive::g()" << endl; }}; int main(){typedef void(*Func)(void); //定义一个函数指针类型 //定义一个子类对象1,通过虚函数表指针调用相应的虚函数Derive derive;long *pvptrderive = (long *)(&derive);long *vptrderive = (long *)(*pvptrderive); //指向类Derive的虚函数表Func f1 = (Func)vptrderive[0]; //指向Base::f()Func f2 = (Func)vptrderive[1]; //指向Derive::g()Func f3 = (Func)vptrderive[2]; //指向Base::h()Func f4 = (Func)vptrderive[3]; //非法Func f5 = (Func)vptrderive[4]; //非法 //定义一个子类对象2,通过虚函数表指针调用相应的虚函数Derive derive2 = derive; //调用拷贝构造函数long *pvptrderive2 = (long *)(&derive2);long *vptrderive2 = (long *)(*pvptrderive2); //此处的虚函数表指针和vprtderive相同 //定义一个父类对象,用子类赋值给父类对象Base base = derive;//直接用子类对象给父类对象值,子类中的属于父类那部分内容会被编译器自动区分(切割)出来并拷贝给了父类对象//所以Base base = derive;实际干了两个事情://第一个事情:生成一个base对象//第二个事情:用derive来初始化base对象的值。//这里编译器给咱们做了一个选择,显然derive初始化base对象的时候,//derive的虚函数表指针值并没有覆盖base对象的虚函数表指针值,即父类对象base有不同于子类对象的虚函数表指针,//这是编译器帮我们做到了这点;long *pvptrbase = (long *)(&base);long *vptrbase = (long *)(*pvptrbase); //指向父类Base的虚函数表Func fb1 = (Func)vptrbase[0]; //指向Base::f()Func fb2 = (Func)vptrbase[1]; //指向Base::g()Func fb3 = (Func)vptrbase[2]; //指向Base::h() return 1;}

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。