请教大家关于虚表的问题

shadow0577 2008-06-20 04:03:21
如果一个类中有虚函数,那么编译器会在这个类的起始位置放入虚表的指针。 但我有个疑问,虚函数的入口地址是在编译时也放入虚表所指向的位置,还是在运行时放呢?
...全文
236 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
shadow0577 2008-06-24
  • 打赏
  • 举报
回复
非常感谢大家的解答,受益匪浅!
晨星 2008-06-23
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 WuBill 的回复:]
运行时动态绑定是骗人的,好多人在这里迷迷糊糊,都是因为中国那些所谓的专家忽悠的!

说什么对于一系列的虚函数,在编译时还不确定调用哪个函数,运行时就知道了,这都是骗人

试想一下,编译器都不能确定将会调用那个函数,那么程序运行之后又如何能确定呢?

其实在函数重载或虚函数当中,编译器早就用按规则改名,或创建虚函数表等方式,清清楚楚地确定将要调用的函数.
[/Quote]
假如我根据用户输入来决定使用哪个子类,编译时如何能确切知道将被调用哪个函数?
编译器确实是不知道的,它只是生成了相关的代码和数据结构,这些代码和数据结构用来保证运行时可以知道。
机智的呆呆 2008-06-23
  • 打赏
  • 举报
回复
对象创建时,调用构造函数时编译器偷偷顺便初始化了 续表
WuBill 2008-06-23
  • 打赏
  • 举报
回复
9楼的讲得很清楚,我又啰嗦了一下
WuBill 2008-06-23
  • 打赏
  • 举报
回复
如果一个类中有被声明为virtual的函数,编译器就会为这个类生成一个虚函数表,也就是VTABLE。
VTABLE实际上是一个函数指针的数组,每个虚函数占用这个数组的一个slot。
一个类只有一个VTABLE,不管它有多少个实例,派生类有自己的VTABLE。
但派生类的VTABLE与基类的VTABLE有相同的函数排列顺序,同名的虚函数被放在两个数组的相同位置上。
在创建类实例的时候,编译器还会在每个实例的内存布局中增加一个vptr字段,该字段指向本类的VTABLE。
通过这些手段,编译器在看到一个虚函数调用的时候,就会将这个调用改写:

比如:
void bar(A * a)
{
a->foo();
}

会被改写为:

void bar(A * a)
{
(a->vptr[1])(); //方括号中值与你整个类A中虚数的排列顺序有关
}

因为派生类和基类的foo()函数具有相同的VTABLE索引,而他们的vptr又指向不同的VTABLE,因此通过这样的方法可以在运行时刻决定调用哪个foo()函数;
但所有的工作都是在编译时完成的,运行时只是根据所给对象所属的类确定到那个虚表里去。

可能大家对编译时和运行时的理解不同吧!呵呵
K行天下 2008-06-22
  • 打赏
  • 举报
回复
当一个类中有虚函数时,编译器会为该类产生一个虚函数表
你可以做一个例子程序, sizeof(class A) (不需要实例化)
A中含有虚函数, 和含有其它一般函数的结果,会发现:存在一个虚函数,size增大4
con_con 2008-06-22
  • 打赏
  • 举报
回复
编译时放进去的吧
fish6344 2008-06-22
  • 打赏
  • 举报
回复
事物本来就没有真假之分,只在于你对它的感受而已!
jieao111 2008-06-22
  • 打赏
  • 举报
回复
http://hi.baidu.com/_586/blog/item/c345aa000fd3c584e950cdc4.html
WuBill 2008-06-21
  • 打赏
  • 举报
回复
运行时动态绑定是骗人的,好多人在这里迷迷糊糊,都是因为中国那些所谓的专家忽悠的!

说什么对于一系列的虚函数,在编译时还不确定调用哪个函数,运行时就知道了,这都是骗人

试想一下,编译器都不能确定将会调用那个函数,那么程序运行之后又如何能确定呢?

其实在函数重载或虚函数当中,编译器早就用按规则改名,或创建虚函数表等方式,清清楚楚地确定将要调用的函数.
fish6344 2008-06-21
  • 打赏
  • 举报
回复
在上述虚函数表slot1中的索引值,对地某些编译器而言,索引值不是0或者是1,这也是有可能的,原因是slot1有可能包含指向类型信息(type_info,用于RTTI)的指针.
shadow0577 2008-06-21
  • 打赏
  • 举报
回复
太感谢了,一直被骗到今天,豁然开朗了。
pengfeiluck 2008-06-20
  • 打赏
  • 举报
回复
整个东东,inside c++ mode。讲的蛮清楚地。建议看下,书也不厚
fish6344 2008-06-20
  • 打赏
  • 举报
回复
虚函数的入口地址是在编译时也放入虚表所指向的位置,还是在运行时放呢?

答:C++类之内存布局,包括数据成员及成员函数之内存布局(具体的内存占位),都可以在编译期硧定,例如:

class A
{
long _a;
public:

virtual void f(){(){//A类f的行为....;}};
};

class B : public A
{
long _b;
public:

void f(){//B类f的行为....;}
};

上述类体糸中的_a、_b、A::f()及B::f()的内存布局都在编译期与以确定(当然是相对地址,实际物理地址要在加载器加载了.exe才能与以确定),这正是C++是静态语言的原因。

而且,虚函数表-这个对程序设计层面隐晦的数据结构及其内存布局(相对地址)也因此能在编译期与以确定.

综上所述,虚函数的地址,虚函数表及其指针vptr都在编译期就与以确定。

运行时,只是根据指针指向的对象类型,寻址到正确的虚函数表,例如:

A a;
B b;

void Test(A* _pa){ _pa->f();}

对于:
Test(&a);

其中的依据_pa将寻址到A的虚函数表vtable_A,编译器将对_pa->f()产生如下伪码:

(*_pa->vtable_a_vptr[0])(_pa);//_pa充当隐晦的this!

对于:
Test(&b);

其中的依据_pa将寻址到B的虚函数表vtable_B,编译器将对_pa->f()产生如下伪码:

(*_pa->vtable_b_vptr[0])(_pa);//_pa充当隐晦的this!

其中怪异的名字也非常可能,当然是mangling的结果(不同的编译器mangling出独特的名字)!注意,其间的差别仅在于_pa指向了不同的虚函数表,而这二个虚函表中的slot1中分别是A::f()和B::f()的地址!

瞧,这就是C++的运行时多态的细部.(请参考《深度探索C++对象模型》候捷译page152-page159)






晨星 2008-06-20
  • 打赏
  • 举报
回复
编译时生成了组织虚函数表的代码。
shadow0577 2008-06-20
  • 打赏
  • 举报
回复
非常感谢大家的热情解答
ysuliu 2008-06-20
  • 打赏
  • 举报
回复
编译的时候放入的。。
运行时的多态是通过查表选择不同的指针来实现的,而不是到时候再往表里放。
www_adintr_com 2008-06-20
  • 打赏
  • 举报
回复
多态是对象的多态不是类的,类的东西(包括虚函数表)都是在编译时候确定的。
动态语言另当别论。
xkyx_cn 2008-06-20
  • 打赏
  • 举报
回复
每个类的虚表在编译时就确定了,标准上说的是:final overrider
就是最终覆盖的虚函数
多态是通过基类指针指向不同的子类地址,获取其vptr(虚表指针)来得到vtbl(虚函数表)
通过虚表获得实际的虚函数地址,再通过此地址调用相应的函数

所以virtual只是一个编译时的概念,到运行时的时候都是通过指针的不同指向来操作的

[Quote=引用 3 楼 coverallwangp 的回复:]
引用 2 楼 xkyx_cn 的回复:
编译时就放好了…………


编译时放的函数地址还没有具体绑定到哪一个函数吧?
[/Quote]
coverallwangp 2008-06-20
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 xkyx_cn 的回复:]
编译时就放好了…………
[/Quote]

编译时放的函数地址还没有具体绑定到哪一个函数吧?
加载更多回复(2)

64,685

社区成员

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

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