我们知道,当您定义A
时,A
的大小会有所不同:
class A
{
short a;
double b;
short c;
};
或者像这样
class A
{
short a;
short c;
double b;
};
我假设我们正在编译32位操作系统,并且我们告诉编译器将对齐到32位。
编译器是否真的很难通过重新排序定义来获得最小尺寸,同时实现相同的性能?
答案 0 :(得分:4)
非常困难。特别需要一个结构来按照与结构定义完全相同的顺序对字段进行排序。
这个要求可能是对Pascal没有这样的要求并导致令人惊讶的结果的反应。
无论如何,并非所有CPU架构都需要对齐或填充。在大多数情况下,它会导致轻微的性能损失。在现代处理器时代,由于CPU流水线的其他方面,内存提取中额外的一个或两个周期可能会消失。
答案 1 :(得分:1)
编译器并不困难,标准禁止(有一个例外): 第9.2.12节:
未声明的(非联合)类的非静态数据成员 中间访问说明符被分配,以便后来的成员具有 类对象中的更高地址。分配顺序 由访问说明符分隔的非静态数据成员未指定
异常,具有不同访问修饰符的成员可以重新安排,所以:
class A
{
public:
int a;
int b;
private:
int c;
int d;
{'\;
a和b,不能重新排序。 c和d,不能重新排序,但(a和b)可以重新排序(c和d)
答案 2 :(得分:0)
由于标准的要求(大致:唯一的增量地址,以及按访问修饰符分组的C ++类),编译器无法重新排序。
这就是重新排序需要手工完成的原因。 一般来说,你很幸运:现有数据类型的更好对齐意味着更小的尺寸和更好的性能,这里没有clnflict。
但是,有时较大的数据(元素)大小意味着更简单的指令。例如,使用位字段来削减几个字节意味着更复杂的代码,在代码大小和数据大小之间进行权衡;将内循环增加500个字节以削减2k数据对于代码的优化和内存局部性可能是灾难性的。
[edit]当结构元素顺序不是最理想且可以改进时,像PVS Studio这样的工具可以发出警告。
[edit2]至于“为什么存在这些规则”:
tl; dr:这很有意思,但并不重要,只是为了好奇。
首先(来自另一个SO答案的无情副本)标准的相关部分:
在结构对象中,非位字段成员和位字段所在的单元具有按声明顺序增加的地址。指向适当转换的结构对象的指针指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。在结构对象中可能有未命名的填充,但不是在它的开头。
指向标准布局结构对象的指针,使用reinterpret_cast进行适当转换,指向其初始成员(或者如果该成员是位字段,则指向它所在的单位),反之亦然。 [注意:因此,在标准布局结构对象中可能存在未命名的填充,但不是在其开头,以实现适当的对齐。 - 后注]
(请注意,这与C,C ++有点复杂。)
标准对原因几乎没有说明,大多数都是“受过教育的猜测”:
不同的地址是标准的其他问题引起的要求。
制作指向struct cast的指针 - 等同于指向第一个成员的指针当然是由于现有的编程习惯。 (它通过将“基础结构”嵌套为“派生结构”的第一个成员来允许C中的“数据多态”)
程序员指定的保留订单:
“vanilla猜测”不会破坏数据局部的手优化。将最常访问的成员置于顶部可以改善结构内的位置(允许更好的缓存或更短的寻址指令)。如果编译器重新排序,则这些优化可能会因总结构大小相对较小的增益而丢失。
要求按访问说明符分组的C ++:(即“所有公共变量合在一起,所有受保护的变量合在一起,所有私有变量在一起):我从未找到理由(我必须说,它是有点令人惊讶。我可以想象(也许)意图是允许编译器实现利用硬件访问控制(“这段代码可能无法访问那段内存”)。我知道没有允许的架构这种精细的控制水平,我从未见过访问说明符被视为一种安全机制。