2011-12-29 56 views
11

我很想知道類將如何安排在內存中。具有繼承和虛擬功能。繼承類的內存佈局

我知道這不是由C++語言標準定義的。但是,是否有任何簡單的方法可以通過編寫一些測試代碼來找出特定的編譯器如何實現這些說明?

編輯: - 使用下面的一些答案: -

#include <iostream> 

using namespace std; 

class A { 
    public: 
    int a; 
    virtual void func() {} 
}; 

class B : public A { 
    public: 
    int b; 
    virtual void func() {} 
}; 

class C { 
    public: 
    int c; 
    virtual void func() {} 
}; 

class D : public A, public C { 
    public: 
    int d; 
    virtual void func() {} 
}; 

class E : public C, public A { 
    public: 
    int e; 
    virtual void func() {} 
}; 

class F : public A { 
    public: 
    int f; 
    virtual void func() {} 
}; 

class G : public B, public F { 
    public: 
    int g; 
    virtual void func() {} 
}; 

int main() { 
    A a; B b; C c; D d; E e; F f; G g; 
    cout<<"A: "<<(size_t)&a.a-(size_t)&a<<"\n"; 
    cout<<"B: "<<(size_t)&b.a-(size_t)&b<<" "<<(size_t)&b.b-(size_t)&b<<"\n"; 
    cout<<"C: "<<(size_t)&c.c-(size_t)&c<<"\n"; 
    cout<<"D: "<<(size_t)&d.a-(size_t)&d<<" "<<(size_t)&d.c-(size_t)&d<<" "<<(size_t)&d.d- (size_t)&d<<"\n"; 
    cout<<"E: "<<(size_t)&e.a-(size_t)&e<<" "<<(size_t)&e.c-(size_t)&e<<" "<<(size_t)&e.e- (size_t)&e<<"\n"; 
    cout<<"F: "<<(size_t)&f.a-(size_t)&f<<" "<<(size_t)&f.f-(size_t)&f<<"\n"; 
    cout<<"G: "<<(size_t)&g.B::a-(size_t)&g<<" "<<(size_t)&g.F::a-(size_t)&g<<" " <<(size_t)&g.b-(size_t)&g<<" "<<(size_t)&g.f-(size_t)&g<<" "<<(size_t)&g.g-(size_t)&g<<"\n"; 
} 

,輸出是: -

A: 8 
B: 8 12 
C: 8 
D: 8 24 28 
E: 24 8 28 
F: 8 12 
G: 8 24 12 28 32 

因此所有的類都在大小爲8的LOC 0獲得了V-PTR D在位置16處有另一個v-ptr。類似地對於E. G在16處似乎也具有v-ptr,但是從我的(有限的)理解中,我會猜測它具有更多。

回答

9

一種方法是打印出所有的成員的偏移量:

class Parent{ 
public: 
    int a; 
    int b; 

    virtual void foo(){ 
     cout << "parent" << endl; 
    } 
}; 

class Child : public Parent{ 
public: 
    int c; 
    int d; 

    virtual void foo(){ 
     cout << "child" << endl; 
    } 
}; 

int main(){ 

    Parent p; 
    Child c; 

    p.foo(); 
    c.foo(); 

    cout << "Parent Offset a = " << (size_t)&p.a - (size_t)&p << endl; 
    cout << "Parent Offset b = " << (size_t)&p.b - (size_t)&p << endl; 

    cout << "Child Offset a = " << (size_t)&c.a - (size_t)&c << endl; 
    cout << "Child Offset b = " << (size_t)&c.b - (size_t)&c << endl; 
    cout << "Child Offset c = " << (size_t)&c.c - (size_t)&c << endl; 
    cout << "Child Offset d = " << (size_t)&c.d - (size_t)&c << endl; 

    system("pause"); 
} 

輸出:

parent 
child 
Parent Offset a = 8 
Parent Offset b = 12 
Child Offset a = 8 
Child Offset b = 12 
Child Offset c = 16 
Child Offset d = 20 

所以,你可以在這裏看到所有的偏移。您會注意到在偏移量0處沒有任何內容,因爲這大概是指向vtable的指針所在的位置。

另請注意,繼承的成員在子項和父項中都具有相同的偏移量。

+1

+1這個示例代碼與我想出的最接近。儘量避免依賴內存佈局。不能保證它在未來版本的編譯器(或者甚至在具有相同編譯器版本的其他上下文中,例如優化)中保持不變。我敢打賭,幾乎總是有更好的方法來解決這個問題。 – Andre 2011-12-29 19:26:02

+0

謝謝。這確實有點幫助。結合你的答案和Azza的... 我也有興趣會發生什麼,如果我們有 A類; B類; B類; C類:公共A,公共B; 這似乎給出了有多個vtable指針的結果。 A的數據成員似乎先於B. – owagh 2011-12-29 19:38:26

+0

我從來沒有處理過多重繼承。但你仍然可以嘗試看看補償顯示的是什麼。我真的不知道多重繼承如何在底下工作。 – Mysticial 2011-12-29 19:40:57

7

Visual Studio atleast有一個hidden compiler option/d1reportSingleClassLayout(開始於〜32:00)。

用法:/d1reportSingleClassLayoutCLASSNAME編譯器開關和CLASSNAME之間不應有空格(顯然將其替換爲您感興趣的類的名稱)。

+0

嘿,這是偉大的,正是我想要的。 像gcc,icc等其他編譯器有類似的功能嗎? – owagh 2011-12-29 19:21:13

+0

@owagh:對不起,我不知道。 :/ – Xeo 2011-12-29 19:24:58

+0

@owagh對於gcc,這似乎並不完美,但它可能仍然有用:http://stackoverflow.com/questions/15951597/what-is-the-linux-aquivalent-to- msvcs-option-d1reportsingleclasslayout(簡寫:在調試模式下用'-g'或'-ggdb'編譯,然後在目標文件上使用'pahole'工具)。 – 2013-08-26 12:00:49

0

最好的方法是編寫幾個簡單的測試用例,然後在彙編器中進行編譯和調試(全部優化關閉):一次運行一條指令,您將看到所有內容都適合。

至少這是我學會的方式。

如果您發現任何情況下特別具有挑戰性,請在SO!

0

只要您堅持單一繼承,子對象通常按照它們聲明的順序排列。指針前置於它們的前面,例如,用於動態調度。一旦多重繼承被加入,事情變得更加複雜,特別是當涉及虛擬繼承時。

要查找至少一個ABI風味的精確信息,您可以查找Itanium ABI。這將記錄所有這些細節。它至少在某些Linux平臺上用作C++ ABI(即,有多個編譯器可以生成鏈接到一個可執行文件中的目標文件)。

要確定佈局只是打印給定對象的子對象的地址。這就是說,除非你碰巧實現了一個編譯器,它通常並不重要。我懷疑對象佈局的唯一真正用途是安排成員來最小化填充。

+0

如果沒有'虛擬'功能,那麼肯定不會添加_vptr_?我試圖得出一個結論性的答案,即POD的單一繼承是否沒有任何'virtual',會導致成員的確切順序,即'base.m1,base.m2,derived.m1,derived.m2'。我知道我可以嘗試一下,看看它是否適用於我的實現,但我真的試圖找到一個有保證的跨平臺方法來做到這一點。 (背景是我映射到二進制格式,其中順序非常重要。) – 2015-12-29 11:44:25

1

創建一個類的對象,將指針指向您的機器的單詞,使用sizeof來查找對象的大小,並檢查該位置的內存。事情是這樣的:

#include <iostream> 

class A 
{ 
public: 
    unsigned long long int mData; 
    A() : 
    mData(1) 
    { 
    }  
    virtual ~A() 
    { 
    } 
}; 
class B : public A 
{ 
public: 
    unsigned long long int mData1; 
    B() : 
    A(), mData1(2) 
    { 
    } 
}; 

int main(void) 
{ 
B lB; 

unsigned long long int * pB = (unsigned long long int *)(&lB); 

for(int i = 0; i < sizeof(B)/8; i++) 
{ 
    std::cout << *(pB + i) << std::endl; 
} 

return (0); 
} 


Program output (MSVC++ x86-64): 

5358814688 // vptr 
1   // A::mData 
2   // B::mData1 

在一個側面說明,斯坦利·B·利普曼擁有出色的書"Inside the C++ Object Model"