标准非常明确,即使是POD结构体(我相信这是最严格的结构体类别之一),成员之间也可以有填充。(“因此,在POD结构体对象中可能存在未命名的填充,但在其开头不会有填充,因为需要实现适当的对齐。”——这是一个非规范性注释,但仍然清楚地表明了意图)。
例如,将标准布局结构体的要求(C++11,§1.8/4)与数组的要求进行对比(§8.3.4/1):
“可平凡复制或标准布局类型(3.9)的对象应占用连续的存储字节。”
“数组类型的对象包含一组N个类型为T的非空子对象,这些对象是连续分配的。”
在数组中,元素本身需要被连续分配,而在结构体中,只需要存储是连续的。
第三种可能性是考虑一个不是平凡可复制或标准布局的结构体/类,这时候存储可能根本不是连续的。例如,实现可能会分别设置一块内存区域来保存所有私有变量和另一块完全独立的内存区域来保存所有公共变量。为了更具体地说明,可以考虑两个定义,如下所示:
class A {
int a;
public:
int b;
} a;
class B {
int x;
public:
int y;
} b;
使用这些定义,内存可能会布局如下:
a.a;
b.x;
// ... somewhere else in memory entirely:
a.b;
b.y;
在这种情况下,元素和存储需求都不需要是连续的,因此交错完全分离的结构/类的部分是允许的。
也就是说,第一个元素必须与整个结构体位于同一地址(9.2/17):"指向POD-struct对象的指针,使用reinterpret_cast适当地转换,指向其初始成员(或如果该成员是位域,则指向它所在的单元),反之亦然。"
在您的情况下,您有一个POD结构体,因此(§9.2/17):“使用reinterpret_cast适当地转换后,指向POD结构体对象的指针指向其初始成员(或者如果该成员是位域,则指向其中所在的单元),反之亦然。”由于第一个成员必须对齐,其余成员都是相同类型,因此除了位域之外,在结构体中放置任何类型都不可能真正需要填充(即,您可以将任何类型放入数组中,其中需要连续分配元素)。如果您有比一个字更小的元素,在面向字的机器上(例如早期DEC Alpha),填充可能会使访问变得更简单。例如,早期DEC Alphas(在硬件级别上)只能一次读/写整个(64位)字。因此,让我们考虑四个char元素的结构体。
struct foo {
char a, b, c, d;
};
如果需要将它们在内存中连续布局,访问
foo::b
(例如)将要求CPU加载该字,然后将其向右移动8位,然后掩码以零扩展该字节以填充整个寄存器。
存储将更糟糕- CPU必须加载整个字的当前值,掩码出该部分恰当大小的char,将新值移动到正确位置,将其OR到字中,最后存储结果。
相比之下,在元素之间加入填充,每个元素都变成了简单的加载/存储,没有移位、掩码等操作。
至少据我所知,DEC Alpha的正常编译器中,
int
是32位,
long
是64位(它早于
long long
)。因此,使用四个
int
的结构体,您可以期望在元素之间看到另外32位的填充(在最后一个元素之后还有32位)。
考虑到您确实有一个POD结构体,您仍然有一些可能性。我可能更喜欢使用offsetof
来获取结构体成员的偏移量,创建一个数组,并通过这些偏移量访问成员。我在之前的几个 答案中展示了如何做到这一点。