2011-12-29 70 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"