跳转至

结构元素顺序和对齐

由于结构只是对象的集合,它们在内存转储中不会明显显现。相反,结构的转储显示的是结构包含的对象。转储看起来很像本章展示的其他转储,但在顺序和对齐上有重要差异。

要看这些差异,先看看清单 4-3。

struct MyStruct {
    unsigned char ubyteValue;
    char byteValue;
    unsigned short uwordValue;
    short wordValue;
    unsigned int udwordValue;
    unsigned long long ulongLongValue;
    long long longLongValue;
    float floatValue;
};
MyStruct& m = 0;
printf("Offsets: %d,%d,%d,%d,%d,%d,%d,%d,%d\n",
    &m->ubyteValue, &m->byteValue,
    &m->uwordValue, &m->wordValue,
    &m->udwordValue, &m->dwordValue,
    &m->ulongLongValue, &m->longLongValue,
    &m->floatValue);

清单 4-3:一个 C++ 结构和一些使用它的代码

这段代码声明了一个名为 MyStruct 的结构,并创建了一个名为 m 的变量,它假想指向地址 0 处的一个结构实例。地址 0 处实际上没有结构实例,但这个技巧让我可以在 printf() 调用中使用取址操作符(&)获取结构中每个成员的地址。由于结构位于地址 0,打印出的每个成员地址等同于它从结构起始处的偏移量。

这个示例的最终目的是确切看到每个成员相对于结构起始处如何在内存中布局。如果你运行代码,会看到以下输出:

Offsets: 0,1,2,4,8,12,16,24,32

如你所见,MyStruct 中的变量完全按代码中定义顺序排列。这种顺序成员布局是结构的强制性属性。把它与清单 4-1 中的示例比较——我们声明了一组相同的变量;在图 4-2 的内存转储中,编译器显然把一些值在内存中放乱了顺序。

此外,你可能注意到成员不像清单 4-1 中全局作用域变量那样对齐;例如,如果对齐,uwordValue 前会有 2 个填充字节。这是因为结构成员按「结构成员对齐」(一个编译器选项,接受 1、2、4、8 或 16 字节;在这个示例中设为 4)或成员大小两者中较小的一个对齐。我安排 MyStruct 的成员,使编译器不需要填充这些值。

然而,如果我们在 ulongLongValue 之后立即放一个 char,printf() 调用会给出以下输出:

Offsets: 0,1,2,4,8,12,16,28,36

现在,同时看看原始和修改后的输出:

Original: Offsets: 0,1,2,4,8,12,16,24,32
Modified: Offsets: 0,1,2,4,8,12,16,28,36

在修改版本中,最后两个值(longLongValue 和 floatValue 从结构起始处的偏移量)发生了变化。感谢结构成员对齐,longLongValue 变量移动了 4 字节(1 个 char 值和它后面的 3 个字节),以确保它被放在能被 4 整除的地址上。