vptr

zcdabing 2014-07-18 03:50:40
多重继承,打个比方,2

class A
{
public:
A(){}
virtual void funA(){}
int a;
virtual ~A(){}
};
classB
{
public:
B(){}
virtual void funB(){}
int b;
virtual ~B(){}
};
class C:public A,B
{
public:
C(){}
void funA(){}
void funB(){}
int b;
virtual ~C(){}
};
int main()
{
C c;
c.funA();
return 0;
}


c.funA()编译器是怎么找到funA的
1遍历 _vptr__A->vtbl_A[1] _vptr__B->vtbl_B[~]
2编译期就知道这个函数在哪个vptr之中

如果大神能回答上面的这个问题 我想问下菱形虚拟继承的子类中的vptr和vptr(b)是怎么分部的
vptr(b)是带有虚基类的偏移量的虚函数表的地址
我分析是这样的:

对象模型: 子类D
子类 dataD D
| vptr(b) B C
| dataC A
| vptrDCA(A-C-D虚函数表转换过程)
| dataB
| vptrDBA(A-B-D虚函数表转换过程)
| dataA
基类类

求教吧




...全文
317 16 打赏 收藏 转发到动态 举报
写回复
用AI写文章
16 条回复
切换为时间正序
请发表友善的回复…
发表回复
阿呆_ 2014-07-21
  • 打赏
  • 举报
回复
C实例化c后结构: c.this-> *vptr_C; ((A)c).this-> *vptr_A; int a; ((B)c).this-> *vptr_B; int b; int c; 声明变量 C obj; 时隐含调用: obj.vptr_C = &C_vfnTable[0]; obj.vptr_A = &C_vfnTable[0]; obj.vptr_B = &C_vfnTable[1]; 编译时: 生成C_vfnTable[2], 复制A_vfnTable到&C_vfnTable[0], 复制B_vfnTable到&C_vfnTable[1], 重置C_vfnTable[0]= C.funcA; 重置C_vfnTable[1]= C.funcB; 将程序中调用代码 所有((C)obj)->funcA() 都转换成 (((C)obj)->vptr->vfnTable[0])(); 所有((C)obj)->funcB() 都转换成 (((C)obj)->vptr->vfnTable[1])(); 所有((A)obj)->funcA()都转换成(((A)obj)->vptr->vfnTable[0])(); 所有((B)obj)->funcB()都转换成(((B)obj)->vptr->vfnTable[0])(); 注意: ((C)obj)后this指向 obj.vptr_C; ((A)obj)后this指向 obj.vptr_A; ((B)obj)后this指向 obj.vptr_B; 如果C中有个独立的虚函数,它可能位于C_vfnTable[0]或C_vfnTable[2] (不同编译器不同实现)
zcdabing 2014-07-21
  • 打赏
  • 举报
回复
引用 10 楼 Idle_ 的回复:
1、不算优化的话,编译器将c.funcA()编译成 c.vptr->vfntable[0]() 2、编译器只知道这个函数处于当前object.vptr->vfntable的第几个函数 3、编译器在C(){}时生成隐含代码执行c.vptr=&class c::vfntable 4、编译时创建class C::vfntable,复制class A::vfntable, 复制classB::vfntable,然后重新将C::vfntable[0]指向C::funcA(), vfntable[1]指向C::funcB() (因为C中override了基类的虚函数,如果没有override, 那么它的原始值就指向基类相应的函数实现)
你说的这些我都知道,C中是有两个虚函数指针的,我想知道调用某个函数的时候是怎么知道从哪个指针调用的 如果C中有个独立的虚函数,他的地址是放在哪个虚函数表里的?
我看你有戏 2014-07-21
  • 打赏
  • 举报
回复
有个虚函数表吧,执行的时候,通过虚函数表进行寻址,这个表应该是在构建对象的时候在内存中生成的吧
阿呆_ 2014-07-21
  • 打赏
  • 举报
回复
另外,vptr是每个实例有一个或多个 -- 分别指向同一个虚函数表不同起始位置以对应不同基类,虚函数表是每个类只有一个。不管你在类里面添加多少虚函数,增加的只是类的虚函数表项。而实例中并不存在任何一个具体虚函数的指针。只有编译器知道具体虚函数在虚函数表中的数组下标。 因此,如下例(也就是你的菱形继承): class A { int a; public: virtual void funcA1(); virtual void funcA2(); }; class B { int b; public: virtual void funcB1(); virtual void funcB2(); } class C: public A, public B { int c; public: virtual void funcC1(); void funcA1(); void funcB2(); }; class D: public C { int d; public: virtual void funcD(); void funcA2(); void funcB1(); }; 那么: 每个类类型中的虚函数表: A.ATable = {A.funcA1, A.funcA2}; B.BTable = {B.funcB1, B.funcB2}; C.CTable = {C.funcC1, C.funcA1, A.funcA2, B.funcB1, C.funcB2}; D.DTable = {D.funcD, C.funcC1, C.funcA1, D.funcA2, D.funcB1, C.funcB2}; 而变量声明: A a; B b; C c; D d; 中各实例的结构: a: { vptr, a} vptr = &A.ATable[0]; b: { vptr, b} vptr = &B.BTable[0]; c: { vptr, vptr_A, a, vptr_B, b, c} vptr = &C.CTable[0]; vptr_A = &C.CTable[1]; vptr_B = &C.CTable[3]; d: { vptr, vptr_C, vptr_A, a, vptr_B, b, c, d} vptr = &D.DTable[0]; vptr_C = &D.DTable[1]; vptr_A = &D.DTable[2]; vptr_B = &D.DTable[4]; 因此当访问 a.funcA1()时,实际访问的是this->vptr->table[0]即ATable[0], 当访问 ((A)d).funcA1()时访问的是this->vptr->table[0], 此时却是DTable[2]而实际调用的是C.funcA1();
阿呆_ 2014-07-21
  • 打赏
  • 举报
回复
每个类各有一个虚函数表,C虚函数表内容=A虚函数表+B虚函数表+C中独立的虚函数,然后C中override的函数会替换相应虚函数表位置的函数指针(即覆盖C虚函数表中复制自A, B的虚函数项)。 关键点:编译器会生成隐含调用将C中个各个vptr都指向C虚函数表不同的位置,然后看你是通过哪个基类调用虚函数,那么就是通过该基类的this指针指向的vptr指向的虚函数表进行访问(再次注意:多继承下类型转换成基类后this指针内容是不同的,实际指向的就是各个基类在实例中的vptr),这样一来访问类型转换基类后虚函数和访问基类实例虚函数的vfnTable位置是一致的,根据不同实例,即使通过基类也可以访问继承类覆盖的虚函数(因为vptr实际指向的是继承类实例的vfnTable)。 至于你说的sizeof,你可以在main中加一句:c.funcB(),然后再看看sizeof(c)。如果没有子类,没有export, 没用到的虚函数包括基类中的vptr都不会存在在类中,这就是所谓的编译器优化了。更深层次的优化甚至你虚继承了,如果没有使用到基类访问,编译器都可能将虚函数调用优化成普通的方法调用而类中根本不存在vptr。毕竟虚函数调用需要运行时绑定,而且调用时间接多次跳转,效率和普通方法根本不能比。
zcdabing 2014-07-21
  • 打赏
  • 举报
回复
引用 13 楼 Idle_ 的回复:
C实例化c后结构: c.this-> *vptr_C; ((A)c).this-> *vptr_A; int a; ((B)c).this-> *vptr_B; int b; int c;
不知道你有没有试过cout<<sizeof(A)<<" "<<sizeof(B)<<" "<<sizeof(C); 4 4 8 还有是不是我说的问题没说清楚呢vptr_C 在C中是不存在的吧 C中只有C版本的 vptr_A vptr_B 才对不是? 如果有个独立的C的虚函数也不会再多个vptr_C ,不然sizeof(C) 就是12了,而会在已有的 vptr_A vptr_B 指向的函数表中加入相应的函数地址才对,所以我觉得你说的
引用 13 楼 Idle_ 的回复:
如果C中有个独立的虚函数,它可能位于C_vfnTable[0]或C_vfnTable[2]
是不对的,还是有我不知道的知识在里面? 如果C中只含有C版本的 vptr_A vptr_B ,我想知道编译器是怎么处理两个虚函数表的。还有 C定义的独立的虚函数会放在哪一个虚函数表中 又或者我都理解错了,请么请解释下VS里面sizeof(C) 为什么是 8 ,而不是12
阿呆_ 2014-07-20
  • 打赏
  • 举报
回复
引用
c.funA()编译器是怎么找到funA的 1遍历 _vptr__A->vtbl_A[1] _vptr__B->vtbl_B[~] 2编译期就知道这个函数在哪个vptr之中
1、不算优化的话,编译器将c.funcA()编译成 c.vptr->vfntable[0]() 2、编译器只知道这个函数处于当前object.vptr->vfntable的第几个函数 3、编译器在C(){}时生成隐含代码执行c.vptr=&class c::vfntable 4、编译时创建class C::vfntable,复制class A::vfntable, 复制classB::vfntable,然后重新将C::vfntable[0]指向C::funcA(), vfntable[1]指向C::funcB() (因为C中override了基类的虚函数,如果没有override, 那么它的原始值就指向基类相应的函数实现)
zcdabing 2014-07-20
  • 打赏
  • 举报
回复
引用 8 楼 boyhailong 的回复:
[quote=引用 1 楼 zhao4zhong1 的回复:] 《深度探索C++对象模型》 《C++反汇编与逆向分析技术揭秘》
是的 看懂了 这些问题都不是事了[/quote] 看来你都看过,回答一下我的问题?
xiaolomg 2014-07-19
  • 打赏
  • 举报
回复
引用 1 楼 zhao4zhong1 的回复:
《深度探索C++对象模型》 《C++反汇编与逆向分析技术揭秘》
是的 看懂了 这些问题都不是事了
Saleayas 2014-07-18
  • 打赏
  • 举报
回复
每一个有虚函数的类都有一个虚表。 这个虚表在类实例化的时候,创建并赋值每一个虚函数。 当这个类被继承后,创建继承类的时候,也是先创建这个虚表,然后赋值每个虚函数,如果继承类有重写虚函数,在继承类构造的时候,重新赋值 。这就是为什么不能再构造之前使用重写的虚函数的原因。因为这个时候虚函数还没有来得及重写。 至于菱形构造,是一样的。菱形构造必须在最终构造时才构造。也就是说一个类虚继承另外一个类是不是在它被构造时构造。 所以创建一个填充虚表是一致的。
赵4老师 2014-07-18
  • 打赏
  • 举报
回复
调皮有理,造反无罪。
zcdabing 2014-07-18
  • 打赏
  • 举报
回复
引用 4 楼 zhao4zhong1 的回复:
计算机组成原理→DOS命令→汇编语言→C语言(不包括C++)、代码书写规范→数据结构、编译原理、操作系统→计算机网络、数据库原理、正则表达式→其它语言(包括C++)、架构…… 对学习编程者的忠告: 眼过千遍不如手过一遍! 书看千行不如手敲一行! 手敲千行不如单步一行! 单步源代码千行不如单步对应汇编一行! VC调试时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化,这样过一遍不就啥都明白了吗。 对VC来说,所谓‘调试时’就是编译连接通过以后,按F10或F11键单步执行一步以后的时候,或者在某行按F9设了断点后按F5执行停在该断点处的时候。 (Turbo C或Borland C用Turbo Debugger调试,Linux或Unix下用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。) 不要迷信书、考题、老师、回帖; 要迷信CPU、编译器、调试器、运行结果。 并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。 任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实! 有人说一套做一套,你相信他说的还是相信他做的? 其实严格来说这个世界上古往今来所有人都是说一套做一套,不是吗? 不要写连自己也预测不了结果的代码!
赵老师又调皮了
赵4老师 2014-07-18
  • 打赏
  • 举报
回复
计算机组成原理→DOS命令→汇编语言→C语言(不包括C++)、代码书写规范→数据结构、编译原理、操作系统→计算机网络、数据库原理、正则表达式→其它语言(包括C++)、架构…… 对学习编程者的忠告: 眼过千遍不如手过一遍! 书看千行不如手敲一行! 手敲千行不如单步一行! 单步源代码千行不如单步对应汇编一行! VC调试时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化,这样过一遍不就啥都明白了吗。 对VC来说,所谓‘调试时’就是编译连接通过以后,按F10或F11键单步执行一步以后的时候,或者在某行按F9设了断点后按F5执行停在该断点处的时候。 (Turbo C或Borland C用Turbo Debugger调试,Linux或Unix下用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。) 不要迷信书、考题、老师、回帖; 要迷信CPU、编译器、调试器、运行结果。 并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。 任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实! 有人说一套做一套,你相信他说的还是相信他做的? 其实严格来说这个世界上古往今来所有人都是说一套做一套,不是吗? 不要写连自己也预测不了结果的代码!
赵4老师 2014-07-18
  • 打赏
  • 举报
回复
我不知道答案,也不能肯定答案就在这两本书里。 但你可以试试: 单步类的实例“构造”或“复制”或“作为函数参数”或“作为函数返回值返回”或“参加各种运算”或“退出作用域”的语句对应的汇编代码几步后,就会来到该类的“构造函数”或“复制构造函数”或“运算符重载”或“析构函数”对应的C/C++源代码处。
zcdabing 2014-07-18
  • 打赏
  • 举报
回复
引用 1 楼 zhao4zhong1 的回复:
《深度探索C++对象模型》 《C++反汇编与逆向分析技术揭秘》
第一本书上没有将这些,《C++反汇编与逆向分析技术揭秘》我没看过不知道 赵老师要是知道答案先告诉我呗,我回去看看第二本书
赵4老师 2014-07-18
  • 打赏
  • 举报
回复
《深度探索C++对象模型》 《C++反汇编与逆向分析技术揭秘》

64,637

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧