我面临以下困境:当我需要申报结构时,我是否
(1)逻辑地对字段进行分组,或
(2)减小尺寸顺序,以节省RAM和ROM大小?
这是一个示例,其中最大的数据成员应位于顶部,但也应与逻辑连接的colour
分组:
struct pixel{
int posX;
int posY;
tLargeType ColourSpaceSecretFormula;
char colourRGB[3];
}
结构的填充是非确定性的(也就是说,取决于实现),所以我们不能可靠地对结构元素进行指针运算(我们不应该:想象有人根据自己的喜好重新排序字段:BOOM,整个代码停止工作)。
-fpack-structs 在gcc中解决了这个问题,但还有其他限制,所以让我们不再讨论编译器选项。
另一方面,代码应首先是可读。应不惜一切代价避免微观优化。
所以,我想知道,为什么结构成员按标准排序,让我担心以特定方式对结构成员进行微观优化?
答案 0 :(得分:3)
编译器受到一些传统和实际限制的限制。
强制转换后的结构指针(标准称它为“适当转换”)将等于指向结构的第一个元素的指针。这通常用于在消息传递中实现消息的重载。在这种情况下,struct具有第一个元素,用于描述结构的其余部分的类型和大小。
最后一个元素可以是动态调整大小的数组。甚至在官方语言支持之前,这经常被用于实践中。您可以分配sizeof(struct) + length of extra data
,并可以将最后一个元素作为普通数组访问,并使用您分配的元素。
这两件事迫使编译器使结构中的第一个和最后一个元素的顺序与它们声明的顺序相同。
另一个实际要求是每个编译必须以相同的方式对结构成员进行排序。智能编译器可以做出决定,因为它看到一些结构成员总是彼此接近访问,所以它们可以以使它们最终进入高速缓存行的方式重新排序。这种优化在C中当然是不可能的,因为结构通常在不同的编译单元之间定义API,我们不能仅仅根据不同的编译重新排序。
我们能做的最好的事情就是在ABI中定义某种包装顺序,以最大限度地减少不接触结构中第一个或最后一个元素的对齐浪费,但它会很复杂,容易出错并且很可能不会买太多。
答案 1 :(得分:2)
如果你不能依赖排序,那么编写将结构映射到硬件寄存器,网络数据包,外部文件格式,像素缓冲区等内容的低级代码会更加困难。
此外,一些代码使用一种技巧,它假定结构的最后一个成员是内存中最高的地址,以表示一个更大的数据块(在编译时大小未知)的开始。
答案 2 :(得分:1)
重新排序结构字段有时会在数据大小方面产生良好的增益,并且通常也会在代码大小方面产生良好的增益,尤其是在64位内存模型中。这里举例说明(假设常见的对齐规则):
struct list {
int len;
char *string;
bool isUtf;
};
将在32位中占用12个字节,在64位模式下占用24个字节。
struct list {
char *string;
int len;
bool isUtf;
};
将在32位中占用12个字节,但在64位模式下仅占16个。
如果你有这些结构的数组,你可以获得50%的数据,但也可以获得代码大小,因为2的幂索引比其他大小更简单。 如果您的结构是单例或不频繁,则重新排序字段没有多大意义。如果它被大量使用,那么就应该看一下。
至于你问题的另一点。为什么编译器不对字段进行重新排序,这是因为在这种情况下,实现使用公共模式的结构的联合将很困难。比如说。
struct header {
enum type;
int len;
};
struct a {
enum type;
int len;
bool whatever1;
};
struct b {
enum type;
int len;
long whatever2;
long whatever4;
};
struct c {
enum type;
int len;
float fl;
};
union u {
struct h header;
struct a a;
struct b b;
struct c c;
};
如果编译器重新排列了字段,这个结构会更加不方便,因为当通过包含在其中的不同结构访问它们时,无法保证type
和len
字段是相同的。 union
。
如果我没记错的话,标准甚至要求这种行为。