你真的在这里问了很多不同的问题,所以我会尽力依次回答每个问题。
首先您想知道数据成员是如何对齐的。成员对齐是编译器定义的,但是由于 CPU 处理未对齐数据的方式,它们都倾向于遵循相同的
结构应根据最严格的成员(通常但不总是最大的内在类型)对齐的准则,并且结构始终对齐,以使数组的元素全部对齐。
例如:
struct some_object
{
char c;
double d;
int i;
};
这个结构将是 24 字节。因为该类包含一个 double 它将是 8 字节对齐的,这意味着 char 将被填充 7 个字节,而 int 将被填充 4 以确保在 some_object 的数组中,所有元素都是 8 字节对齐的(大小一个对象的总是它的对齐的倍数)。一般来说,这取决于编译器,尽管您会发现对于给定的处理器架构,大多数编译器对齐数据相同。
您提到的第二件事是派生类成员。派生类的排序和对齐有点痛苦。类单独遵循我上面描述的结构规则,但是当你开始谈论继承时,你就会陷入混乱的地盘。给定以下类:
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 的内存布局是:
int i // base
派生的内存布局是:
int i // base
int k // derived
derived2 的内存布局为:
int i // base
int k // derived
int l // derived2
derived3 的内存布局为:
int i // base
int k // derived
int i // base
int k // derived
int l // derived2
int m // derived3
您可能会注意到,base 和derived 都在这里出现了两次。这就是多重继承的奇妙之处。
为了解决这个问题,我们有虚拟继承。
derived4 的内存布局为:
void* base_ptr // implementation defined ptr that allows to find base
int n // derived4
int i // base
derived5 的内存布局为:
void* base_ptr // implementation defined ptr that allows to find base
int o // derived5
int i // base
derived6 的内存布局为:
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 函数时,它都有一个对象可以传递给这些函数。这个结构依赖于编译器,因为它没有在语言规范中指定,但几乎所有编译器都实现它。
当您开始谈论虚函数时,事情会变得更加复杂,但同样,大多数编译器也以相同的方式实现它们。参加以下课程:
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
{
};
这些类中的每一个都至少包含一个虚函数。
vbase 的内存布局是:
void* vfptr // vbase
vbase2 的内存布局是:
void* vfptr // vbase2
vderived 的内存布局为:
void* vfptr // vderived
vderived2 的内存布局为:
void* vfptr // vbase
void* vfptr // vbase2
关于 vftables 的工作原理,人们有很多不明白的地方。首先要了解的是,类只存储指向 vftable 的指针,而不是整个 vftable。
这意味着无论一个类有多少个虚函数,它都只会有一个 vftable,除非它通过多重继承从其他地方继承了一个 vftable。几乎所有编译器都将 vftable 指针放在类的其他成员之前。这意味着您可能在 vftable 指针和类的成员之间有一些填充。
我还可以告诉您,几乎所有编译器都实现了编译指示包功能,允许您手动强制结构对齐。一般来说,除非你真的知道自己在做什么,否则你不想这样做,但它就在那里,而且有时是必要的。
您问的最后一件事是您是否可以控制订购。您始终控制排序。编译器将始终按照您编写它们的顺序对事物进行排序。我希望这个冗长的解释能够满足您需要了解的所有内容。