#include <cstdio>
#include <iostream>
#include <typeinfo>
using namespace std;
class Point
{
public:
Point()
{
cout<<"Point constructor"<<endl;
}
virtual void func_hs()
{
cout<<"Point::func_hs"<<endl;
printf("the address of this --func_hs:%p\n",&Point::func_hs);
}
virtual void func_zzy()
{
cout<<"Point::func_zzy"<<endl;
printf("the address of this --func_zzy:%p\n",&Point::func_zzy);
}
static void print()
{
//相對地址,在虛表指針之後.0x4 0x8 0xc --------->point to member
printf("&Point::x=%p\n&Point::y=%p\n&Point::z=%p\n",
&Point::x,&Point::y,&Point::z);
}
void printThis()
{
//float *
printf("&this->x=%p\n&this->y=%p\n&this->z=%p\n",
&this->x,&this->y,&this->z);
}
void printVt()
{
printf("the address of object,this:%p\nthe address of vt:%p\n",
this,(void*)*(int*)this);
}
void callVtFuncs(int num=2)
{
cout<<endl<<endl;
typedef void (*Funp)(void);
for(int i=0;i<num;i++)
{
Funp funp=(Funp)*((int*)*(int*)this+i);
printf("%p\n",((int*)*(int*)this+i));
printf("Point::callVtFuncs=>address of this fun:%p\n",funp);
if(i==2||i==3)
{
continue;
}
funp();
}
}
void printVirtualFunAddress()
{
cout<<endl<<endl;
printf("func_hs:%p\nfunc_zzy:%p\nfunc_zzzy:%p\n",
&Point::func_hs,&Point::func_zzy,
&Point::func_zzzy);
printf("%p\n",&Point::func_zzzy);
}
virtual ~Point()
{
// printf("%p\n",&Point::~Point);
cout<<"Point destructor"<<endl;
}
virtual void func_zzzy()
{
cout<<"Point::func_zzzy"<<endl;
printf("the address of this --func_zzzy:%p\n",&Point::func_zzzy);
}
protected:
float x,y,z;
};
int main(int argc, char *argv[])
{
Point point;
Point::print();
point.printThis();
point.printVt();
point.callVtFuncs(5);
point.printVirtualFunAddress();
printf("sizeof func:%u\n",sizeof(&main));
printf("%p\n",&main);
printf("sizeof memfunc:%u\n",sizeof(&Point::printVirtualFunAddress));
printf("%p\n",&Point::printVirtualFunAddress);
printf("%p\n",&Point::func_zzzy);
printf("sizeof virtmemfunc:%u\n",sizeof(&Point::func_zzzy));
cout<<typeid(point).name()<<endl;
return 0;
}
Point類裡面包括virtual desctructor共有4個virtual functions.
輸出結果如下:
Point constructor
&Point::x=0x4
&Point::y=0x8
&Point::z=0xc
&this->x=0xbffff624
&this->y=0xbffff628
&this->z=0xbffff62c
the address of object,this:0xbffff620
the address of vt:0x8048fc0
0x8048fc0
Point::callVtFuncs=>address of this fun:0x8048a12
Point::func_hs
the address of this --func_hs:0x1
0x8048fc4
Point::callVtFuncs=>address of this fun:0x8048a64
Point::func_zzy
the address of this --func_zzy:0x5
0x8048fc8
Point::callVtFuncs=>address of this fun:0x8048c8e
0x8048fcc
Point::callVtFuncs=>address of this fun:0x8048cda
0x8048fd0
Point::callVtFuncs=>address of this fun:0x8048cf8
Point::func_zzzy
the address of this --func_zzzy:0x11
func_hs:0x1
func_zzy:(nil)
func_zzzy:0x5
0x11
sizeof func:4
0x804880c
sizeof memfunc:8
0x8048bdc
0x11
sizeof virtmemfunc:8
data member那一塊比較簡單,沒有太大的問題。主要是virtual table這塊比較復雜 。第一個小問題就是
順序的問題。實驗證明在vtable中虛函數指針的順序是按照聲明時的順序放置的。不管virtual desctructor
在哪個位置,都會占據兩個slot,即有兩個virtual desctructor.至於為什麼會有兩個,具體我也不是太清楚,
後面在討論。
我第一個有一個有疑問的地方就是關於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 <Point::func_hs()> 0x8048a64 <Point::func_zzy()> 0x8048c8e <Point::~Point()> 0x8048cda <Point::~Point()>
0x8048fd0 <_ZTV5Point+24>: 0x8048cf8 <Point::func_zzzy()> 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>,這個應該是編譯器生成的,再往深處我也不甚清楚了。
作者:西城