为什么sizeof(Derived4)是8字节?我认为它应该是5个字节

时间:2013-11-23 13:51:37

标签: c++ sizeof virtual-inheritance memory-layout empty-class

这是给定程序的输出:

sizeof(Empty) 1
sizeof(Derived1) 1
sizeof(Derived2) 4
sizeof(Derived3) 1
sizeof(Derived4) 8
sizeof(Dummy) 1

这是该计划:

#include <iostream>
using namespace std;

class Empty
{};

class Derived1 : public Empty
{};

class Derived2 : virtual public Empty
{};

class Derived3 : public Empty
{    
    char c;
};

class Derived4 : virtual public Empty
{
    char c;
};

class Dummy
{
    char c;
};

int main()
{
    cout << "sizeof(Empty) " << sizeof(Empty) << endl;
    cout << "sizeof(Derived1) " << sizeof(Derived1) << endl;
    cout << "sizeof(Derived2) " << sizeof(Derived2) << endl;
    cout << "sizeof(Derived3) " << sizeof(Derived3) << endl;
    cout << "sizeof(Derived4) " << sizeof(Derived4) << endl;    
    cout << "sizeof(Dummy) " << sizeof(Dummy) << endl;

    return 0;
}

Derived3的大小为1个字节。那么为什么Derived 4的大小是8个字节?如果对齐是答案,那么为什么在derived3?

的情况下没有对齐

2 个答案:

答案 0 :(得分:5)

它取决于类中数据成员的对齐方式。似乎如果一个类有一个虚拟基类,那么它的实现包含对这个虚拟基类的引用,在你的情况下它等于4个字节。当您添加char类型的数据成员时,它将填充三个字节,以提供对基本虚拟类的引用的对齐。

答案 1 :(得分:0)

对于具体类型 Tsizeof表示两件事:

  • a类型的完整对象 T的表示仅占{sizeof(T)中的(char*)&a个字节,{{1 }});

  • (char*)&a + sizeof(T)数组将第二个对象T存储在第一个对象之后。

完整对象占用的字节不重叠:一个是另一个的主题并包含在其中,或者它们没有共同的字节。

您可以覆盖一个完整的对象(使用sizeof(T)),然后使用placement new来重构它(或者只是为没有有意义构造的对象分配),如果析构函数不重要,一切都会好的(如果析构函数负责释放资源,请不要这样做)。您不能仅覆盖基类子对象,因为它会破坏整个对象。 memset告诉您在不破坏其他对象的情况下可以覆盖多少字节。

类的数据成员是完整的对象,因此类的大小始终至少是其成员大小的总和

某些类型是&#34; full&#34;:对象中的每一位都是有意义的;值得注意的是,sizeof。某些类型具有未使用的位或字节。许多班级都有这样的&#34;洞&#34;用于填充。空类具有零有意义的位:没有位是状态的一部分,因为没有状态。空类是具体的类,但是实例化了;每个实例都有一个标识,因此具有不同的地址,因此即使标准允许unsigned char的零值,其大小也不能为零。空类是纯填充。

考虑:

sizeof

struct intchar { int i; char c; }; 的对齐方式是intchar的对齐方式。在int为4的典型系统上,这些基本类型的对齐方式与大小相等, 所以sizeof(int)具有对齐4和大小8,因为大小对应于两个数组元素之间的距离,因此3个字节不用于表示。

给定intchar

intchar_char

大小必须大于struct intchar_char { intchar ic; char c; }; 的大小,即使intchar中存在未使用的字节,因为对齐:成员ic是一个完整的对象并占用其所有字节,{此对象允许{1}}。

ic仅适用于具体类型(可以实例化)和完整对象。因此,如果要创建这样的数组,则需要memset来确定空类的大小;但是对于基类子对象,sizeof并没有为您提供所需的信息。

C ++中没有运算符可以测量类表示中使用的字节数,但是您可以尝试使用派生类:

sizeof

请注意,sizeof没有template <class Base, int c=1> struct add_chars : Base { char dummy[c]; }; template <class T> struct has_trailing_unused_space { static const bool result = sizeof (add_chars<T>) == sizeof (T); }; 类型的成员,因此没有add_chars<T>个完整对象,T不允许T 1}}子对象。 memset是一个完整的对象,不能与任何其他完整对象重叠,但它可以与基类子对象重叠。

派生类的大小并不总是至少是其子项目大小的总和。

成员intchar只占用一个字节;如果dummy中有任何尾随字节,大多数编译器将在未使用的空间中分配dummy; Base测试此属性。

dummy

outputs

  

空有尾随空格:1

虚拟继承

在考虑涉及虚函数和虚基类的类的布局时,需要考虑隐藏的vptr和内部指针。在典型的实现中,它们将具有与has_trailing_unused_space相同的属性(大小和对齐)。

int main() {
    std::cout << "empty has trailing space: ";
    std::cout << has_trailing_unused_space<empty>::result;
}

与普通继承和成员资格不同,虚拟继承并不定义严格的直接所有权关系,但是共享的间接所有权就像调用虚函数一样引入了间接。虚拟继承创建了两种类布局:基类子对象和完整的对象布局。

当一个类被实例化时,编译器将使用为完整对象定义的布局,可以使用vptr作为GCC,并且Titanium ABI规定:

void*

vptr指向一个包含所有运行时信息的完整vtable,但C ++语言并不认为这样的类是多态类,因此class Derived2 : virtual public Empty {}; / struct Derived2 { void *__vptr; }; 可以&#39;用于确定动态类型。

AFAIK,Visual C ++不使用vptr,而是指向子对象的指针:

dynamic_cast

其他编译器可以使用相对偏移量:

typeid

struct Derived2 { Empty *__ptr; }; 非常简单; struct Derived2 { offset_t __off; }; 的子对象布局与其完整的对象布局相同。

不考虑稍微复杂的案件:

Derived2

这里Derived2的完整布局可能是(Titanium ABI风格):

struct Base {
    int i;
};

struct DerV : virtual Base {
    int j;
};

子对象布局

DerV

struct complete__DerV { void *__vptr; int j; Base __base; }; 类型的所有完整或不完整对象都具有此布局。

vtable包含虚拟基础的相对偏移量:struct DerV { void *__vptr; int j; }; ,如果是动态类型DerV的对象。

可以通过在运行时查找覆盖者或通过语言规则了解动态类型来完成对虚函数的调用。

向上转换(指向虚拟基类的指针),当在基类上调用成员函数时,它经常隐式发生:

offsetof(complete__DerV,__base)

在动态类型已知时使用已知偏移量,如此处,或使用运行时信息来确定偏移量:

DerV

可以翻译成(Titanium ABI-style)

struct Base {
    void f();
};

struct DerV : virtual Base {
};

DerV d;
d.f(); // involves a derived to base conversion

或Visual C ++ - 样式:

void foo (DerV &d) {
    d.f(); // involves a derived to base conversion
}

甚至

void foo (DerV &d) {
    (Base*)((char*)&d + d.__vptr.off__Base)->f();
}

开销取决于实施,但只要动态类型不为人知,它就会存在。