C:数据结构对齐

我正在使用结构,并对它们有几个问题。 据我所知,结构变量将按顺序放在内存中。 块长度(字)取决于机器架构(32位 – 4字节,64位 – 8字节)。

可以说我们有2个数据结构:

struct ST1 { char c1; short s; char c2; double d; int i; }; 

在内存中它将是:

 32 bit - 20 bytes 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ------------------------------------------------------------------------------------------ c1| PB| s | s | c1| PB| PB| PB| d | d | d | d | d | d | d | d | i | i | i | i | 64 bit - 24 bytes | 20 | 21 | 22 | 23 | previous sequence + --------------------- | PB | PB | PB | PB | 

但我们可以重新排列它,使这些数据适合机器字。 像这样:

 struct ST2 { double d; int i; short s; char c1; char c2; }; 

在这种情况下,对于32位和64位,它将以相同的方式表示(16字节):

  0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ---------------------------------------------------------------------- d | d | d | d | d | d | d | d | i | i | i | i | s | s | ch1| ch2| 

我有一些问题:

  • 这就像疯狂的猜测,但struct主要规则是在开始时定义更大尺寸的变量?
  • 据我所知,它不适用于独立变量。 像char str[] = "Hello";
  • 填充字节,它有什么代码? 它在ASCII表的某个地方吗? 对不起,找不到了。
  • 2个结构,所有成员在内存中用不同的地址表示,它们可以不按顺序放在内存中?
  • 这样的结构: struct ST3 { char c1; char c2; char c3;} st3; struct ST3 { char c1; char c2; char c3;} st3; size = 3 ,我知道如果我们将其他类型的成员添加到其中,它将被对齐。 但为什么它之前没有对齐呢?

基本规则很简单:

  • 成员必须按顺序存在(除非在C ++中使用private:public:… sections)
  • 成员之间和最后一个之后允许填充

就是这样。 剩下的就是实现:类型占用的存储量,填充量。 通常,您可以期望它在ABI中直接记录或直接在编译器中记录,甚至可以使用工具进行操作。

实际上,在某些体系结构中需要填充,例如SPARC要求32位 “整数”在可被4整除的地址上对齐。在其他情况下,它不是必需的,但未对齐的实体可能需要更多时间来处理,比如80286处理器需要一个额外的周期来从奇数地址读取16位实体。 (在我忘记之前:类型本身的表示是不同的!)

通常,对齐要求或最佳性能完全匹配:您应在与边界相同的边界上对齐。 一个好的反例是80位浮点数(在某些编译器中可用为double或long double),类似于8或16字节对齐而不是10。

要填充padding编译器通常会给你一个设置默认值的开关。 这在不同版本之间有所不同,因此最好在升级时考虑到。 并且内部代码覆盖设施,例如gcc中的 _attribute__(packed)MS中的 #pragma pack以及许多其他设施。 这些都是标准的扩展。

最重要的是,如果你想摆弄布局,你现在和将来都会开始阅读你所针对的所有编译器的dox,以了解它们的作用以及如何控制它。 可能还会读取目标平台的dox,这取决于您首先对布局感兴趣的原因。

一个通常的动机是在将原始内存写入文件并希望将其读回时具有稳定的布局。 也许在不同的平台上使用不同的编译 在新平台类型进入场景之前,这是更容易的。

其他动机是表现。 随着规则的快速变化,效果难以立即预测,这一点更加棘手。 在英特尔说,基本的“未对齐”惩罚已经消失了很长时间,相反,重要的是在缓存行内。 缓存行大小因处理器而异。 使用更多填充也可以产生更好的个体,而完全打包的结构在缓存使用中更经济。

并且一些操作需要正确对齐,但不是由编译器直接强制执行,您可能需要应用特殊的对齐编译指示(例如某些与SSE相关的东西)。

底线重复:停止猜测,决定你的目标并阅读正确的dox。 (顺便说一句,对我来说,阅读SPARCIA32和其他人的架构手册非常有趣,并且在很多方面都获得了很多好处。)

回答你提出的问题(忽略你对结构的非常好的描述)

这就像疯狂的猜测,但结构的主要规则是在开始时定义更大尺寸的变量?

始终把需要最多对齐的东西放在第一位。 例如,我不会首先使用char[99] 。 一般来说,这可以作为指针,64位本机类型,32位本机类型等,但如果您的结构包含其他结构的成员,则必须非常小心。

据我所知,它不适用于独立变量。 像char str[] = "Hello";

我真的不明白这一点。 如果在堆栈上定义char数组,则它具有char对齐。 如果你定义一个char数组后跟一个int,那么堆栈上可能会有填充,你只是找不到它。

填充字节,它有什么代码? 它在ASCII表的某个地方吗? 对不起,找不到了。

它既没有代码也没有数据。 它是编译器插入的填充,并且可以包含任何值,在程序的相同或不同运行中,结构的不同实例之间可能有差异,也可能不同。

2个结构,所有成员在内存中用不同的地址表示,它们可以不按顺序放在内存中?

我不明白。 您是否询问编译器是否可以在结构之间插入填充? 如果没有,请澄清,因为这个答案不会有太大帮助;

当编译器创建一个结构时,它必须使您能够合理地创建这样的结构数组。 考虑一下:

 struct S { int wibble; char wobble; }; S stuff[2]; 

如果编译器在摆动后没有插入3个字节的填充,则对stuff[1].wobble访问将无法正确对齐,这将导致某些硬件崩溃(以及其他硬件上的恶劣性能)。 基本上,编译器必须确保最后的填充,以确保结构的最对齐成员始终正确对齐此类结构的数组。

这样的结构: struct ST3 { char c1; char c2; char c3;} st3; struct ST3 { char c1; char c2; char c3;} st3; 大小= 3,我知道如果我们将其他类型的成员添加到其中,它将被对齐。 但为什么它之前没有对齐呢?

你的意思是’为什么编译器没有把它放在一个正确对齐的地方’? 因为语言不允许它。 不允许编译器对结构的成员重新排序。 它只允许插入填充。

结构(和类)成员的对齐取决于platform,true,但也取决于编译器。 将成员对齐其大小的原因是出于性能原因。 使所有整数类型与其大小对齐会减少内存访问。

您通常可以强制编译器减少对齐,但除了特定原因(例如,不同平台之间的数据兼容性,作为通信数据),这不是一个好主意。 在Visual C ++中存在#pragma pack ,例如:

 #pragma pack(1) struct ST1 { char c1; short s; char c2; double d; int i; }; assert(sizeof(ST1) == 16); 

但正如我之前所说,通常不是一个好主意。

请记住,编译器不仅仅是在某些字段之后添加填充字节。 它还确保结构在内存中分配,所有字段都是右对齐的。 我的意思是,在你的ST1示例中,因为较大的字段类型是double,编译器将确保d字段将以8字节对齐(除非使用#pragma pack或类似选项):

 ST1 st1; assert(&st1.d % 8 == 0); 

关于你的问题:

  • 如果你想节省空间,是的,按尺寸排序是一个很好的技巧,先写一个更大的。 在组合结构的情况下,使用内部结构的较大字段的大小,而不是结构的大小。
  • 它正在研究独立变量。 但是编译器可以在内存中对变量进行排序(而不是结构和类的成员)。

例如:

 short s[27]; int32_t i32[34]; int64_t i64[45]; assert(s % 2 == 0); assert(i32 % 4 == 0); assert(i64 % 8 == 0); 
  • 填充字节可以包含任何内容。 通常是初始化数据(至少你初始化它)。 出于调试原因,有时可能会由编译器包含特定的字节模式。
  • 关于所有成员在不同地址的内存中表示的结构:对不起,我不太清楚你在问什么。
  • 标准C ++表示结构/类的地址必须与此类结构/类的第一个字段的地址相同。 然后,只有在c3之后可能填充,但从未在c1之前填充。

从N3337(C ++ 11)[9.2 class.menu,p.20]:

指向标准布局结构对象的指针(使用reinterpret_cast适当转换)指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。 [注意:因此,在标准布局结构对象中可能存在未命名的填充,但不是在其开头,以实现适当的对齐。 – 尾注]

对于英特尔架构上的gcc,它需要更多的指令和周期来访问(读/写)奇数编号的存储器地址。 所以padding被添加到achive偶数内存地址

要小心,你不确定你的变量是否对齐(但通常是这样)。 如果使用GCC,则可以使用打包的属性以确保数据对齐。

示例:

 struct foo { char c; int x; } __attribute__((packed)); 

据我所知,它不适用于独立变量。 像char str [] =“你好”;?

此表将在您的记忆中对齐。