首页 新闻 会员 周边

C++ 虚函数表的问题,能解答出,你就算牛人了

1
悬赏园豆:100 [待解决问题]

class A

{

  public:

    virtual void f(){}

}

 

class B

{

  public:

    virtual void g(){}

}

 

class A:public A,public B

{

  public:

    virtual void h(){}

    virtual void f(){}

    virtual void g(){}

}

 

很简单吧,一个多重继承的例子

 

C类对象布局,最上面的是 A的子对象,含有一个vptr ,紧接着是 B的子对象 还有一个vptr

 

C * pc = new C();

B * pb = pc;

pc指向的是C对象的首地址,会经过调整后赋值给pb,让pb指向的是 C对象中B的子对象首地址

pb->g();

pb通过vptr扎到虚函数表第一位置的函数,该函数为子类C的g函数,运行时调用,实现多态

 

现在有个疑问

pc->g();

pc的地址就是C对象的首地址,它对应的vptr是

里面不包含g函数,请问这个是如何检索到g函数的
似是故人来的主页 似是故人来 | 初学一级 | 园豆:100
提问于:2010-08-27 18:37
< >
分享
所有回答(3)
0

你看看下面的描述 http://en.wikipedia.org/wiki/Virtual_method_table

Consider the following class declarations in C++ syntax:

class B1
{
public:
void f0() {}
virtual void f1() {}
int int_in_b1;
};

class B2
{
public:
virtual void f2() {}
int int_in_b2;
};

used to derive the following class:

class D : public B1, public B2
{
public:
void d() {}
void f2() {} // override B2::f2()
int int_in_d;
};

and the following piece of C++ code:

B2 *b2 = new B2();
D *d = new D();

G++ 3.4.6 from GCC produces the following 32 bit memory layout for the object b2:[nb 1]

b2:
+0: pointer to virtual method table of B2
+4: value of int_in_b2

virtual method table of B2:
+0: B2::f2()

and the following memory layout for the object d:

d:
+0: pointer to virtual method table of D (for B1)
+4: value of int_in_b1
+8: pointer to virtual method table of D (for B2)
+12: value of int_in_b2
+16: value of int_in_d

Total size: 20 Bytes.

virtual method table of D (for B1):
+0: B1::f1() // B1::f1() is not overridden

virtual method table of D (for B2):
+0: D::f2() // B2::f2() is overridden by D::f2()

Note that those functions not carrying the keyword virtual in their declaration (such as f0() and d()) do not generally appear in the vtable. There are exceptions for special cases as posed by the default constructor.

Overriding of the method f2() in class D is implemented by duplicating the virtual method table of B2 and replacing the pointer to B2::f2() with a pointer to D::f2().

[edit] Multiple inheritance and thunks

The G++ compiler implements the multiple inheritance of the classes B1 and B2 in class D using two virtual method tables, one for each base class. (There are other ways to implement multiple inheritance, but this is the most common.) This leads to the necessity for "pointer fixups" (thunks) when casting.

Consider the following C++ code:

D  *d  = new D();
B1 *b1 = static_cast<B1*>(d);
B2 *b2 = static_cast<B2*>(d);

While d and b1 will point to the same memory location after execution of this code, b2 will point to the location d+8 (eight bytes beyond the memory location of d). Thus, b2 points to the region within d which "looks like" an instance of B2, i.e., has the same memory layout as an instance of B2.

Jake Lin | 园豆:365 (菜鸟二级) | 2010-08-27 21:55
关于Thunks,这是关键。 Thunks in object-oriented programming Some compilers for object-oriented languages such as C++ generate functions called "thunks" as an optimization of virtual function calls in the presence of multiple or virtual inheritance. Consider the C++ code struct A { int value; virtual int access() { return this->value; } }; struct B { int value; virtual int access() { return this->value; } }; struct C : public A, public B { int better_value; virtual int access() { return this->better_value; } }; int use(B *b) { return b->access(); } ... C c; use(&c); ... Since the function B::access is virtual, the call to b->access() requires a vtable dispatch. In naïve implementations, the dispatch will consist of five steps: 1. The object b holds a pointer to the vtable. Load that pointer into a register. 2. The vtable entry for B::access is at some known offset in the vtable for B; find that entry E. 3. E contains a pointer to a function (in this case, C::access). Load that function pointer C::access. 4. Since C::access expects a this pointer to an instance of C, but b is an instance of B, we must decrement b by the offset of B in C (in this example, probably 8 bytes: the size of C::A::value plus the size of C::A's vtable pointer). Since this offset is not known to use at compile time, it must also be loaded from E. 5. Finally, call C::access with the adjusted value of b. The fourth step, in which an offset is loaded from E and added to b, can be completely eliminated by the compiler, thus speeding up every virtual method call, if the compiler generates a wrapper function like this, and places its address in the vtable entry E: int thunk_for_C_access_in_B(B *b) { C *adjusted_b = (C *)b; /* decrements "b" by 8 */ return adjusted_b->C::access(); /* a [[tail call]] to the original method */ } Then the steps for use()
支持(0) 反对(0) Jake Lin | 园豆:365 (菜鸟二级) | 2010-08-27 22:03
0

还真不好跟你解释,因为你拿结果来质疑流程。

你再仔细看下子类 C 的虚函数表,你会发现你在子类 C 中定义的虚函数 h() 也不见了吧!那 pc->h() 这是如何检索到的呢?

你能由 pb 看到那个虚函数表,是因为你的 pb 实际上是 pc 的地址空间的虚函数表,如果你这样:

B* pb =new B();

你再看 pb 的虚函数表,你就会发现 g() 没了。

虚函数表中始终记录的是父类的虚函数,如果子类重载了父类的虚函数,在父类的虚函数表中被重载的那个虚函数的地址就会被子类的函数的地址替代。

Launcher | 园豆:45045 (高人七级) | 2010-08-27 22:41
“子类 C 的虚函数表,你会发现你在子类 C 中定义的虚函数 h() 也不见了”。乱来。。 你先去了解C++对象的内存布局,然后再来回答跟C++对象内存布局相关的问题。
支持(0) 反对(0) 烛秋 | 园豆:200 (初学一级) | 2010-08-29 23:42
@烛秋:大哥,你看到它的图了吗?我反正是没看到,我是根据它的描述推断,他使用了VS的调试状态下查看变量,你也可以去看下 vptr 数组里绝对没有 h().
支持(0) 反对(0) Launcher | 园豆:45045 (高人七级) | 2010-08-30 08:54
0

g()在B中声明为虚函数,所以B的空间中会有一个虚函数表,里面记录着g()的地址,对B来说,这个个地址就指向g()函数在B中的实现。

C继承自B,也继承了B的这个虚函数表,里面也记录着g()的地址,只是对C来说,这个地址指向的是g()函数在C中的实现。

pc->g()就是这样找到g()的。

花无形 | 园豆:279 (菜鸟二级) | 2012-10-19 22:27
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册