2012-08-09 80 views
1

我總是困惑的C的存儲器佈局++虛擬table.Here是示例代碼 我使用來研究它:C++對虛擬表虛擬descructors和類的類型信息

#include <cstdio> 
#include <iostream> 
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); 
    } 


    void printVt() 
    { 
     printf("the address of object,this:%p\nthe address of vt:%p\n", 
       this,(void*)*(int*)this); 
    } 
    void callVtFuncs(int num=2) 
    { 
     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\n",&Point::func_hs,&Point::func_zzy); 
    } 
    virtual ~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.printVt(); 
    point.callVtFuncs(5); 
    point.printVirtualFunAddress(); 
    return 0; 
} 

我把4個虛函數的類,並打印出有解決information.Here是outout:

Point constructor 
the address of object,this:0xbffff620 
the address of vt:0x8048db8 


0x8048db8 
Point::callVtFuncs=>address of this fun:0x8048914 
Point::func_hs 
the address of this --func_hs:0x1 
0x8048dbc 
Point::callVtFuncs=>address of this fun:0x8048966 
Point::func_zzy 
the address of this --func_zzy:0x5 
0x8048dc0 
Point::callVtFuncs=>address of this fun:0x8048b0a 
0x8048dc4 
Point::callVtFuncs=>address of this fun:0x8048b56 
0x8048dc8 
Point::callVtFuncs=>address of this fun:0x8048b74 
Point::func_zzzy 
the address of this --func_zzzy:0x11 


func_hs:0x1 
func_zzy:(nil) 
func_zzzy:0x5 
Point destructor 

,我完全不明白爲什麼最後的輸出是「funz_zzy:(無)」和'funz_zzy:0x5' 但上面的是0x5和0x11。

下面是一些調試信息:(Linux的32位)

(gdb) x/16a 0x8048da8 
0x8048da8: 0xa7025 0x0 0x0 0x8048dd4 <_ZTI5Point> 
0x8048db8 <_ZTV5Point+8>: 0x8048914 <Point::func_hs()> 0x8048966 <Point::func_zzy()> 0x8048b0a <Point::~Point()> 0x8048b56 <Point::~Point()> 
0x8048dc8 <_ZTV5Point+24>: 0x8048b74 <Point::func_zzzy()> 0x696f5035 0x746e 0x804a248 <[email protected]@CXXABI_1.3+8> 
0x8048dd8 <_ZTI5Point+4>: 0x8048dcc <_ZTS5Point> 0x3b031b01 0x80 0xf 

我想不通爲什麼有兩個點::〜點()? 0x804a248處的信息代表類的類型信息?

其他的一些信息:

(gdb) x/1a 0x8048dd4 
0x8048dd4 <_ZTI5Point>: 0x804a248 <[email protected]@CXXABI_1.3+8> 

用途是什麼0x8048dd4?

+2

您可以使用['-fdump級-hierarchy'] (http://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html)轉儲類佈局,這可能有助於/解釋一些東西。 – DCoder 2012-08-09 08:05:15

+0

我希望你不要在代碼中的任何位置依賴你的vtable佈局,因爲這不能跨平臺和編譯器移植。 – Damian 2012-08-09 08:39:23

+0

沒有「C++ vtable佈局」。該標準沒有定義任何這樣的事情,甚至沒有定義vtable本身的概念。 如果你想學習說gcc-4.7 vtable佈局,這是完全沒問題,但它不一定會轉化爲其他編譯器和版本。 – 2012-08-09 09:02:07

回答

1

我完全不明白爲什麼最後的輸出是'funz_zzy:(nil)'和'funz_zzy:0x5'但上面的是0x5和0x11。

這是因爲在一個情況下,你使用printf()使用多個參數,而在其他國家只有一個。並且由於指向成員的指針似乎是8字節長度類型,所以可以得到這個額外的零。試試這個代碼:

printf("sizeof func:%u\n",sizeof(&main)); 
printf("sizeof memfunc:%u\n",sizeof(&Point::printVirtualFunAddress)); 
printf("sizeof virtmemfunc:%u\n",sizeof(&Point::func_zzzy)); 

對我來說,打印

sizeof func:4 
sizeof memfunc:8 
sizeof virtmemfunc:8 

我想不通爲什麼有兩個點::〜點()?

看起來像這與它是虛擬的連接。如果刪除virtual關鍵字,則只會生成一個析構函數。第二個與第一個是相同的,但它另外調用某些數據上的free()函數。我沒有弄清楚這是什麼。

並且在0x804a248處的信息表示類的類型信息?

這應該是你得到的結構typeid(Point)的結果。

+0

但是我怎樣才能區分兩個析構函數?我不知道如何獲得它們的身體? – 2012-08-09 09:10:22

+0

'objdump -d ./a.exe | grep PointD'爲我輸出'PointD0'和'PointD1'。第一個特定於虛擬析構函數,地址低於第二個。 – xaizek 2012-08-09 09:15:39

+0

謝謝。我已經試過了,它有幫助。 – 2012-08-09 09:24:14

2

首先,當然:你在做什麼是完全未定義的行爲,並且理論上可能發生任何事情。但我懷疑你知道這一點,並且 是「試驗」,以便找出編譯器會做什麼。 實際上,無論你看到的將會非常依賴於 編譯器—我猜你在英特爾上使用g ++,因爲我知道其他編譯器會有 你看到的顯着不同的輸出。

至於爲什麼最後的輸出是奇怪的:還有一個未定義的 行爲給你。您正在使用"%p"輸出,這意味着您需要通過void*,或者您有未定義的行爲。實際上, 很可能用於指向非成員函數的指針(並且 Posix或多或少需要它工作),但指向成員函數 的指針則完全不同。通常,指向成員函數 (如&Point::func_hs)的指針將對應於某種結構,其中 是某種附加信息,用於指示函數 是否爲虛擬函數,以及vtable中的索引(如果函數 是虛擬的)或函數的物理地址。通常(但是 再次,這取決於實現),指向 成員函數的指針的大小將大於指向非成員 (或指向靜態成員的指針)的指針的大小。

如果你的目標是理解的佈局,我會傾實際內存 十六進制,使用sizeof(&Point::func_hs)等喜歡的東西:

template <typename T> 
class DumpAsUInt 
{ 
    T const& myValue; 
public: 
    DumpAsUInt(T const& value) : myValue(value) {} 
    friend std::ostream& operator<<(std::ostream& dest, DumpAsUInt const& obj) 
    { 
     unsigned const* p = (unsigned const*)(&obj.myValue); 
     for (int i = 0; i != sizeof(T)/sizeof(unsigned); ++ i) { 
      if (i != 0) { 
       dest << ' '; 
      } 
      dest << p[i]; 
     } 
     return dest; 
    } 
}; 

template <typename T> 
DumpAsUInt<T> 
dumpAsUInt(T const& value) 
{ 
    return DumpAsUInt<T>(value); 
}