关于C++ 虚函数表的一些问题(G++)(二)

2014-11-24 11:08:51 · 作者: · 浏览: 4
体我也不是太清楚,
后面在讨论。
我第一个有一个有疑问的地方就是关于typeinfo的问题。记得《Inside the c++ object model》中,Lippman
提到的对象模型中,将typeinfo放置在vtable的第一个slot中,而根据上面例子来看,第一个slot就是所声明
的第一个virtual function.那么typeinfo存在何处?

vtable 的首地址是0x8048fc0,而从0x8048fb0开始的内存地址信息如下:

(gdb) x/16a 0x8048fb0
0x8048fb0: 0x75253a63 0xa 0x0 0x8048fdc <_ZTI5Point>
0x8048fc0 <_ZTV5Point+8>: 0x8048a12 0x8048a64 0x8048c8e 0x8048cda
0x8048fd0 <_ZTV5Point+24>: 0x8048cf8 0x696f5035 0x746e 0x804a4c8 <_ZTVN10__cxxabiv117__class_type_infoE@@CXXABI_1.3+8>
0x8048fe0 <_ZTI5Point+4>: 0x8048fd4 <_ZTS5Point> 0x3b031b01 0x98 0x12

可以看出,在vtable之前也有关于Point类的信息,0x8048fbc处的值为0x8048fdc <_ZTI5Point>,而0x8048fdc处的值为:0x804a4c8 <_ZTVN10__cxxabiv117__class_type_infoE@@CXXABI_1.3+8,可以猜测这是与类型有关的。


输出中我在typeid(point)那一行设了断点,进入函数之后可以看到:

(gdb) s
std::type_info::name (this=0x8048fdc)
at /usr/lib/gcc/i686-pc-linux-gnu/4.7.1/../../../../include/c++/4.7.1/typeinfo:102
102 { return __name[0] == '*' __name + 1 : __name; }
(gdb) n
5Point

point的类型信息为5Point,而type_info::name传入的参数信息就是0x8048fdc.再看用
-fdump-class-hierarchy输出的关于Point的类型信息:


Vtable for Point
Point::_ZTV5Point: 7u entries
0 (int (*)())0
4 (int (*)())(& _ZTI5Point)
8 (int (*)())Point::func_hs
12 (int (*)())Point::func_zzy
16 (int (*)())Point::~Point
20 (int (*)())Point::~Point
24 (int (*)())Point::func_zzzy

Class Point
size=16 align=4
base size=16 base align=4
Point (0xb60019a0) 0
vptr=((& Point::_ZTV5Point) + 8u)


这样的结果已经相当清楚。在g++的实现中,真正的typeinfo信息放置在vtable之后,其位置是通过vtable之前的地址内
所包含的信息所指定。在整个关于Point类的这些信息里,起始位置为&Point::_ZTV5Point,其值为0x0,之后是关于类型
信息,然后才是vtable的入口点。即 vptr=((& Point::_ZTV5Point) + 8u)。point类的this指针通过类型转化后所解
引用得到的值即使vptr.至于size=16很好理解,1个vptr+3个float.


另一个诡异的信息就是point to member的输出,单个输出是,3个virtual function分别是0x1 0x5 0x11,而在
printVirtualFunAddress中所输出的信息却很诡异,分别是0x1,nil,0x5,这就是为什么要在main函数中加入测试
函数指针大小的原因,普通函数是4个字节,而成员函数的确是8个字节,不管是不是virtual function.所以在printf
参数入栈的时候,要push两次,将第二次push的结果当作了第二个参数的值,以此后推。所以会有上面的结果。

最后就是那两个desctructor的问题,在上面通过函数指针调用函数的时候要将他们都略过去。如果将virtual去掉,
那么vtable中就没有desctructor。
下面是objdump出来的结果:objdump -d a.out|grep PointD

8048932: e8 51 03 00 00 call 8048c88 <_ZN5PointD1Ev>
8048944: e8 3f 03 00 00 call 8048c88 <_ZN5PointD1Ev>
08048c88 <_ZN5PointD1Ev>:
8048cc5: 74 0b je 8048cd2 <_ZN5PointD1Ev+0x4a>
08048cd4 <_ZN5PointD0Ev>:
8048ce0: e8 a3 ff ff ff call 8048c88 <_ZN5PointD1Ev>


两个函数中,main调用的是<_ZN5PointD1Ev>,这就是我们上面声明的那个virtual desctrucotr.而且在内存布局中,
<_ZN5PointD1Ev>的位置比较靠前,其在vtable中的位置也应在<_ZN5PointD0Ev>之前,而<_ZN5PointD0Ev>

08048cd4 <_ZN5PointD0Ev>:
8048cd4: 55 push %ebp
8048cd5: 89 e5 mov %esp,%ebp
8048cd7: 83 ec 18 sub $0x18,%esp
8048cda: 8b 45 08 mov 0x8(%ebp),%eax
8048cdd: 89 04 24 mov %eax,(%esp)
8048ce0: e8 a3 ff ff ff call 8048c88 <_ZN5PointD1Ev>
8048ce5: 8b 45 08 mov 0x8(%ebp),%eax
8048ce8: 89 04 24 mov %eax,(%esp)
8048ceb: e8 80 f9 ff ff call 8048670 <_ZdlPv@plt>
8048cf0: c9 leave www.2cto.com

的工作好像也要调用<_ZN5PointD1Ev>,这个应该是编译器生成的,再往深处我也不甚清楚了。

作者:西城