具有“空类”的C ++多继承存储器布局 [英] C++ Multiple Inheritance Memory Layout with "Empty classes"

查看:148
本文介绍了具有“空类”的C ++多继承存储器布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道多重继承的内存布局没有定义,所以我不应该依赖它。但是,我可以依靠它在一个特殊的情况。也就是说,一个类只有一个真正的超类。所有其他都是空类,即既没有字段也没有虚拟方法(即它们只有非虚方法)的类。在这种情况下,这些附加类不应该向类的内存布局添加任何内容。 (更简洁地说,在C ++ 11的写法中,类有 standard-layout



我可以推断所有的超类都有没有偏移?例如:

  #include< iostream> 

class X {

int a;
int b;
};

class I {};

class J {};

class Y:public I,public X,public J {};

int main(){

Y * y = new Y();
X * x = y;
I * i = y;
J * j = y;

std :: cout<< sizeof(Y) std :: endl
<< y < std :: endl
<< x<< std :: endl
<< i<< std :: endl
<< j<< std :: endl;
}

这里, Y X 是唯一真正的基类的类。程序的输出(当使用g ++ 4.6在linux上编译时)如下:


8



0x233f010



0x233f010



0x233f010



0x233f010


我的结论是,没有指针调整。但是这个实现具体或者我可以依赖它。也就是说,如果我收到一个类型为 I 的对象(我知道只有这些类存在),我可以使用 reinterpret_cast 将它转换为 X



我的希望是,我可以依赖它,因为规范说,对象的大小必须至少是一个字节。因此,编译器不能选择另一个布局。如果它将 X I J >,那么它们的大小将为零(因为它们没有成员)。因此,唯一合理的选择是对齐所有超级类没有偏移。



我是正确的,或者我玩火,如果我使用reinterpret_cast从 I X 这里?

解决方案>在C ++ 11中,编译器需要为标准布局类型使用空基类优化。请参阅 http://stackoverflow.com/a/10789707/981959



对于您的特定示例,所有类型都是标准布局类,并且没有公共基类或成员(见下文),因此您可以依赖于C ++ 11中的该行为 实践,我想许多编译器已经遵循了这条规则,当然G ++做了,其他人遵循 Itanium C ++ ABI 。)



注意:确保没有任何相同类型的基类,因为它们必须位于不同的地址,例如

  struct I {}; 

struct J:I {};
struct K:I {};

struct X {int i; };

struct Y:J,K,X {};

#include< iostream>

Y y;

int main()
{
std :: cout< & y<< ''<< & y.i<< ''<< (X *)& y< ''<< (I *)(J *)& y < ''<< (I *)(K *)& y < '\\\
';

}

列印:

  0x600d60 0x600d60 0x600d60 0x600d60 0x600d61 

类型 Y 只有一个 I 可以偏移零,因此尽管 X offsetof(Y,i)为零),并且 I / code> bases是在相同的地址,但另一个 I 基地(至少与G ++和Clang ++)一个字节到对象,所以如果你有 X * 您不能 reinterpret_cast 因为你不知道 I 它指向的子对象, I 在偏移量0或 I 在偏移量1处。



编译器可以将第二个<$ c在 int )的偏移量$ c> I 子对象,因为 I 没有非静态数据成员,因此您不能实际解除引用或访问该地址上的任何内容,只能获取该地址处的对象的指针。如果您向 I 添加非静态数据成员,则 Y 将不再是标准布局, EBO和 offsetof(Y,i)将不再为零。


I know the memory layout of multiple inheritance is not defined, so I should not rely on it. However, can I rely on it in a special case. That is, a class has only one "real" super class. All others are "empty classes", i.e., classes that neither have fields nor virtual methods (i.e. they only have non-virtual methods). In this case, these additional classes should not add anything to the memory layout of the class. (More concisely, in the C++11 wording, the class has standard-layout)

Can I infer that all the superclasses will have no offset? E.g.:

#include <iostream>

class X{

    int a;
    int b;
};

class I{};

class J{};

class Y : public I, public X,  public J{};

int main(){

    Y* y = new Y();
    X* x = y;
    I* i = y;
    J* j = y;

    std::cout << sizeof(Y) << std::endl 
                  << y << std::endl 
                  << x << std::endl 
                  << i << std::endl 
                  << j << std::endl;
}

Here, Y is the class with X being the only real base class. The output of the program (when compiled on linux with g++4.6) is as follows:

8

0x233f010

0x233f010

0x233f010

0x233f010

As I concluded, there is no pointer adjustment. But is this implementation specific or can I rely on it. I.e., if I receive an object of type I (and I know only these classes exist), can I use a reinterpret_cast to cast it to X?

My hopes are that that I could rely on it because the spec says that the size of an object must at least be a byte. Therefore, the compiler cannot choose another layout. If it would layout I and J behind the members of X, then their size would be zero (because they have no members). Therefore, the only reasonable choice is to align all super classes without offset.

Am I correct or am I playing with the fire if I use reinterpret_cast from I to X here?

解决方案

In C++11 the compiler is required to use the Empty Base-class Optimization for standard layout types. see http://stackoverflow.com/a/10789707/981959

For your specific example all the types are standard layout classes and don't have common base classes or members (see below) so you can rely on that behaviour in C++11 (and in practice, I think many compilers already followed that rule, certainly G++ did, and others following the Itanium C++ ABI.)

A caveat: make sure you don't have any base classes of the same type, because they must be at distinct addresses, e.g.

struct I {};

struct J : I {};
struct K : I { };

struct X { int i; };

struct Y : J, K, X { };

#include <iostream>

Y y;

int main()
{
  std::cout << &y << ' ' << &y.i << ' ' << (X*)&y << ' ' << (I*)(J*)&y << ' ' << (I*)(K*)&y << '\n';

}

prints:

0x600d60 0x600d60 0x600d60 0x600d60 0x600d61

For the type Y only one of the I bases can be at offset zero, so although the X sub-object is at offset zero (i.e. offsetof(Y, i) is zero) and one of the I bases is at the same address, but the other I base is (at least with G++ and Clang++) one byte into the object, so if you got an I* you couldn't reinterpret_cast to X* because you wouldn't know which I sub-object it pointed to, the I at offset 0 or the I at offset 1.

It's OK for the compiler to put the second I sub-object at offset 1 (i.e. inside the int) because I has no non-static data members, so you can't actually dereference or access anything at that address, only get a pointer to the object at that address. If you added non-static data members to I then Y would no longer be standard layout and would not have to use the EBO, and offsetof(Y, i) would no longer be zero.

这篇关于具有“空类”的C ++多继承存储器布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆