4

这是给定程序的输出:

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 的情况下没有对齐?

4

2 回答 2

5

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

于 2013-11-23T13:58:32.503 回答
1

对于具体类型 Tsizeof意味着两件事:

  • 类型的完整对象 的表示只占用[ , )中的字节;aTsizeof(T)(char*)&a(char*)&a + sizeof(T)

  • 存储第一个对象之后的第二个对象的数组。Tsizeof(T)

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

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

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

有些类型是“完整的”:对象中的每一位都是有意义的;值得注意的是,unsigned char. 某些类型具有未使用的位或字节。许多类都有这样的填充“洞”。空类的有意义位为零:没有位是状态的一部分,因为没有状态。空类是具体类,但已实例化;每个实例都有一个标识,因此有一个不同的地址,因此即使标准允许sizeof. 空类是纯填充。

考虑:

struct intchar {
    int i;
    char c;
};

的对齐intchar是 的对齐int。在典型系统中,其中sizeof(int)4 并且这些基本类型的对齐等于大小,intchar对齐 4 和大小 8 也是如此,因为大小对应于两个数组元素之间的距离,因此不使用 3 个字节来表示。

给定intchar_char

struct intchar_char {
    intchar ic;
    char c;
};

由于对齐intchar的原因,即使存在未使用的字节,其大小也必须大于其大小:成员是一个完整的对象并占据其所有字节,并且在该对象中是允许的。icicmemset

sizeof仅针对具体类型(可以实例化)和完整对象定义良好。因此sizeof,如果要创建此类数组,则需要确定空类的大小;但对于基类子对象,sizeof并没有给你你想要的信息。

C++ 中没有运算符来衡量一个类的表示中使用了多少字节,但您可以尝试使用派生类:

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>它没有 type 的成员T,因此没有T完整的对象,并且memset不允许在intchar子对象上使用。dummy是一个完整的对象,不能与任何其他完整对象重叠,但可以与基类子对象重叠。

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

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

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

输出

空有尾随空格:1

虚拟继承

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

class Derived2 : virtual public Empty
{};

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

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

struct Derived2 {
    void *__vptr;
};

vptr 指向一个完整的vtable,包含所有的运行时信息,但是C++语言不认为这样的类是多态类,所以dynamic_cast/typeid不能用来判断动态类型。

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

struct Derived2 {
    Empty *__ptr;
};

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

struct Derived2 {
    offset_t __off;
};

Derived2是很简单的类;的子对象布局Derived2与其完整的对象布局相同。

不考虑一个稍微复杂的案例:

struct Base {
    int i;
};

struct DerV : virtual Base {
    int j;
};

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

struct complete__DerV {
    void *__vptr;
    int j;
    Base __base;
};

子对象布局是

struct DerV {
    void *__vptr;
    int j;
};

所有类型的完整或不完整对象DerV都具有此布局。

vtable 包含虚拟基础的相对偏移量:offsetof(complete__DerV,__base)如果是动态类型的对象DerV

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

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

struct Base {
    void f();
};

struct DerV : virtual Base {
};

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

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

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

可以翻译成(Titanium ABI-style)

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

或 Visual C++ 风格:

void foo (DerV &d) {
    d.__ptr->f();
}

甚至

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

开销取决于实现,但只要动态类型未知,就会出现开销。

于 2015-12-07T21:18:09.530 回答