C++对象内存布局

3

我正在尝试理解C++中多重继承的对象布局。

为此,我有两个超类A、B和一个子类C。

当我试图进行转储时,我期望看到的是:

vfptr | A的字段 | vfptr | B的字段 | C的字段。

我得到了这个模型,但是其中有一些我不理解的零。

以下是我正在尝试的代码:

    #include <iostream>

    using namespace std;

    class A{
       public:
       int a;
       A(){ a = 5; }
       virtual void foo(){ }
    }; 

    class B{
       public:
       int b;    
       B(){ b = 10; }
       virtual void foo2(){ }
    };

    class C : public A, public B{
       public:
       int c;    
       C(){ c = 15; a = 20;}
       virtual void foo2(){ cout << "Heeello!\n";}
    };

   int main()
  {
    C c;
    int *ptr;

ptr = (int *)&c;
cout << *ptr << endl;
ptr++;
cout << *ptr << endl;
ptr++;
cout << *ptr << endl;
ptr++;
cout << *ptr << endl;
ptr++;
cout << *ptr << endl;
ptr++;
cout << *ptr << endl;
ptr++;
cout << *ptr << endl;
ptr++;
cout << *ptr << endl;
ptr++;
cout << *ptr << endl;

       return 0;
   }

这是我得到的输出结果:
4198384     //vfptr
0
20          // value of a
0
4198416     //vfptr
0
10          // value of b
15          // value of c

在这些零之间的意义是什么? 提前感谢!

1
C++ 中并不存在所谓的“对象布局”。实际上,存在的是“你的编译器在本周在你的平台上生成的对象布局”。 - n. m.
1
什么是中间的零的意思?提前感谢!对齐填充。这取决于编译器! - πάντα ῥεῖ
@n.m. 如果你想要至少一些最小的编译器交互,那么它最好是与上周和下周在此平台上生成的相同布局。如果您希望不同的编译器为此平台生成相同的布局,则最好是相同的布局。 - David Rodríguez - dribeas
这取决于架构和平台,你应该将它们添加到问题中。例如,在英特尔的Windows/64和Linux/32上的布局将会非常不同,并且很可能与Solaris上的sparc/64也不同... - David Rodríguez - dribeas
@DavidRodríguez-dribeas 那确实会非常好,但是并没有任何这样的保证。 - n. m.
显示剩余2条评论
6个回答

2

这在很大程度上取决于架构和编译器... 对您而言,指针的大小可能不等于int的大小... 您使用的是哪种架构/编译器?


2
如果您正在使用64位系统,则:
  • 第一个零是第一个vfptr的4个最高有效字节。

  • 第二个零是填充,以使第二个vfptr对齐到8字节地址。

  • 第三个零是第二个vfptr的4个最高有效字节。

您可以检查sizeof(void*) == 8来断言。

没错,我刚刚检查过了。非常感谢! - ginou

2
那取决于你的编译器。使用clang-500,我得到如下结果:
191787296
1
20
0
191787328
1
10
15
1785512560

我确信GDB也有类似的方法,但这是我使用LLDB在类对象地址处转储指针大小的字词得到的结果:

0x7fff5fbff9d0: 0x0000000100002120 vtable for C + 16
0x7fff5fbff9d8: 0x0000000000000014
0x7fff5fbff9e0: 0x0000000100002140 vtable for C + 48
0x7fff5fbff9e8: 0x0000000f0000000a

这种布局看起来很合理,对吧?就像你期望的那样。但是在程序中它显示不够清晰,因为你正在转储 int 大小的字。在 64 位系统上,sizeof(int)==4 但 sizeof(void*)==8。所以,你会看到指针被分成 (int,int) 对。在 Linux 上,你的指针没有任何比低 32 位更高的位设置,而在 OSX 上我的指针确实有 - 这就是 0 和 1 不同的原因。

编译器:g++(Ubuntu/Linaro 4.6.3-1ubuntu5)4.6.3。所以我理解0被用作一种分隔符? - ginou
@Griwes:你在使用LLVM svn还是发布的clang? - Enrico Granata

1
如果您使用MVSC,则可以使用-d1reportAllClassLayout命令将解决方案中所有类的内存布局全部转储出来,方法如下:
cl -d1reportAllClassLayout main.cpp 希望对您有所帮助。

1

不知道您使用的平台和编译器是什么,很难判断,但这可能是一个对齐问题。实际上,编译器可能会尝试将类数据沿着8字节边界对齐,用零进行填充。

没有上述细节,这只是一种猜测。


1
这完全取决于你的编译器、系统和位数。
虚函数表指针的大小将与指针大小相同。这取决于您将文件编译为32位还是64位。指针也将在其大小的多个地址上对齐(就像任何类型通常会这样做一样)。这可能是您在 20 后看到0填充的原因。
整数将具有特定系统上整数的大小。这通常始终为32位。请注意,如果在您的计算机上不是这种情况,则会得到意外的结果,因为您正在使用指针算术将ptr增加sizeof(int)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接