C++数据对齐/成员顺序&遗产 [英] c++ data alignment /member order & inheritance

查看:26
本文介绍了C++数据对齐/成员顺序&遗产的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果使用继承/多重继承,数据成员如何对齐/排序?这个编译器是特定的吗?

How do data members get aligned / ordered if inheritance / multiple inheritance is used? Is this compiler specific?

有没有办法在派生类中指定成员(包括来自基类的成员)的排序/对齐方式?

Is there a way to specify in a derived class how the members (including the members from the base class) shall be ordered / aligned?

推荐答案

你真的在这里问了很多不同的问题,所以我会尽我所能依次回答每一个问题.

Really you’re asking a lot of different questions here, so I’m going to do my best to answer each one in turn.

首先你想知道数据成员是如何对齐的.成员对齐是编译器定义的,但由于 CPU 处理未对齐数据的方式,它们都倾向于遵循相同的

First you want to know how data members are aligned. Member alignment is compiler defined, but because of how CPUs deal with misaligned data, they all tend to follow the same

结构应该根据最严格的成员(通常但不总是最大的内部类型)对齐的准则,并且结构总是对齐的,这样数组的元素都对齐相同.

guideline that structures should be aligned based on the most restrictive member (which is usually, but not always, the largest intrinsic type), and strucutres are always aligned such that elements of an array are all aligned the same.

例如:

struct some_object
{
    char c;
    double d;
    int i;
};

这个结构体将是 24 个字节.因为该类包含一个 double 它将是 8 字节对齐的,这意味着 char 将被填充 7 个字节,而 int 将被填充 4 以确保在 some_object 的数组中,所有元素都将是 8 字节对齐(大小对象的始终是其对齐的倍数).一般来说,这取决于编译器,尽管您会发现对于给定的处理器架构,大多数编译器对齐数据都是一样的.

This struct would be 24 bytes. Because the class contains a double it will be 8 byte aligned, meaning the char will be padded by 7 bytes, and the int will be padded by 4 to ensure that in an array of some_object, all elements would be 8 byte aligned (the size of an object is always a multiple of its alignment). Generally speaking this is compiler dependent, although you will find that for a given processor architecture, most compilers align data the same.

你提到的第二件事是派生类成员.派生类的排序和对齐有点痛苦.类单独遵循我上面为结构描述的规则,但是当您开始谈论继承时,您会陷入混乱的境地.给定以下类:

The second thing you mention is derived class members. Ordering and alignment of derived classes is kinda a pain. Classes individually follow the rules I described above for structs, but when you start talking about inheritance you get into messy turf. Given the following classes:

class base
{
    int i;
};

class derived : public base // same for private inheritance
{
    int k;
};

class derived2 : public derived
{
    int l;
};

class derived3 : public derived, public derived2
{
    int m;
};

class derived4 : public virtual base
{
    int n;
};

class derived5 : public virtual base
{
    int o;
};

class derived6 : public derived4, public derived5
{
    int p;
};

base 的内存布局为:

The memory layout for base would be:

int i // base

派生的内存布局为:

int i // base
int k // derived

derivative2 的内存布局为:

The memory layout for derived2 would be:

int i // base
int k // derived
int l // derived2

derived3 的内存布局为:

The memory layout for derived3 would be:

int i // base
int k // derived
int i // base
int k // derived
int l // derived2
int m // derived3

您可能会注意到 base 和derived 在这里出现了两次.这就是多重​​继承的奇迹.

You may note that base and derived each appear twice here. That is the wonder of multiple inheritance.

为了解决我们有虚拟继承的问题.

To get around that we have virtual inheritance.

derived4 的内存布局为:

The memory layout for derived4 would be:

void* base_ptr // implementation defined ptr that allows to find base
int n // derived4
int i // base

derivative5 的内存布局是:

The memory layout for derived5 would be:

void* base_ptr // implementation defined ptr that allows to find base
int o // derived5
int i // base

derived6 的内存布局为:

The memory layout for derived6 would be:

void* base_ptr // implementation defined ptr that allows to find base
int n // derived4
void* base_ptr2 // implementation defined ptr that allows to find base
int o // derived5
int i // base

您会注意到派生的 4、5 和 6 都有一个指向基础对象的指针.这是必要的,以便在调用 base 的任何函数时,它都有一个对象要传递给这些函数.这个结构是编译器相关的,因为它没有在语言规范中指定,但几乎所有的编译器都实现了它.

You will note that derived 4, 5, and 6 all have a pointer to the base object. This is necessary so that when calling any of base's functions it has an object to pass to those functions. This structure is compiler dependent because it isn't specified in the language spec, but almost all compilers implement it the same.

当您开始谈论虚函数时,事情变得更加复杂,但同样,大多数编译器也以相同的方式实现它们.参加以下课程:

Things get more complicated when you start talking about virtual functions, but again, most compilers implement them the same as well. Take the following classes:

class vbase
{
    virtual void foo() {}
};

class vbase2
{
    virtual void bar() {}
};

class vderived : public vbase
{
    virtual void bar() {}
    virtual void bar2() {}
};

class vderived2 : public vbase, public vbase2
{
};

这些类中的每一个都包含至少一个虚函数.

Each of these classes contains at least one virtual function.

vbase 的内存布局为:

The memory layout for vbase would be:

void* vfptr // vbase

vbase2 的内存布局为:

The memory layout for vbase2 would be:

void* vfptr // vbase2

vderived 的内存布局为:

The memory layout for vderived would be:

void* vfptr // vderived

vderived2 的内存布局为:

The memory layout for vderived2 would be:

void* vfptr // vbase
void* vfptr // vbase2

关于 vftables 的工作原理,人们有很多不了解的地方.首先要理解的是,类只存储指向 vftables 的指针,而不是整个 vftables.

There are a lot of things people don't understand about how vftables work. The first thing to understand is that classes only store pointers to vftables, not whole vftables.

这意味着无论一个类有多少个虚函数,它都只会有一个vftable,除非它通过多重继承从其他地方继承了一个vftable.几乎所有编译器都将 vftable 指针放在类的其余成员之前.这意味着您可能在 vftable 指针和类成员之间有一些填充.

What that means is that no matter how many virtual functions a class has, it will only have one vftable, unless it inherits a vftable from somewhere else via multiple inheritance. Pretty much all compilers put the vftable pointer before the rest of the members of the class. That means that you may have some padding between the vftable pointer and the class's members.

我还可以告诉您,几乎所有编译器都实现了 pragma pack 功能,允许您手动强制结构对齐.一般来说,除非您真的知道自己在做什么,否则您不会想要这样做,但它就在那里,有时是必要的.

I can also tell you that almost all compilers implement the pragma pack capabilities which allow you to manually force structure alignment. Generally you don't want to do that unless you really know what you are doing, but it is there, and sometimes it is necessary.

您问的最后一件事是您是否可以控制订购.您始终可以控制订购.编译器总是按照你写的顺序来排序.我希望这个冗长的解释能满足你需要知道的一切.

The last thing you asked is if you can control ordering. You always control ordering. The compiler will always order things in the order you write them in. I hope this long-winded explanation hits everything you need to know.

这篇关于C++数据对齐/成员顺序&遗产的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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