请查看以下代码示例,使用Visual Studio 2010在Windows-32系统上执行:
#include <iostream>
using namespace std;
class LogicallyClustered
{
bool _fA;
int _nA;
char _cA;
bool _fB;
int _nB;
char _cB;
};
class TypeClustered
{
bool _fA;
bool _fB;
char _cA;
char _cB;
int _nA;
int _nB;
};
int main(int argc, char* argv[])
{
cout << sizeof(LogicallyClustered) << endl; // 20
cout << sizeof(TypeClustered) << endl; // 12
return 0;
}
这两个类的sizeof
不同,因为编译器正在插入填充字节以实现变量的优化内存对齐。这是对的吗?
如果我按照class TypeClustered
中的类型对变量进行聚类,为什么内存占用量会减小?
总是根据类型对成员变量进行聚类是一个很好的经验法则吗? 我是否应该根据它们的大小升序(bool,char,int,double ......)对它们进行排序?
修改
较小的内存占用将提高数据缓存效率,因为可以缓存更多对象,并避免完全内存访问“慢速”RAM。那么成员声明的排序和分组是否可以被视为(小)但易于实现的性能优化?
答案 0 :(得分:5)
1)绝对正确。
2)它不小,因为它们是分组的,但是因为它们有序和分组的方式。例如,如果一个接一个地声明4 chars
,则它们可以打包成4个字节。如果您声明一个char
并且立即声明一个int
,则将插入3个填充字节,因为int
将需要与4个字节对齐。
3)不!您应该在一个类中对成员进行分组,以使该类变得更具可读性。
另一个注意事项 - 在某些平台上也存在一些小的性能提升,用于访问驻留在类实例的第一个n
(变化)字节中的成员。因此,在课程开始时声明经常访问的成员可以导致小的速度增加。但是,这也不应该是一个标准。我只是陈述一个事实,但绝不建议你这样做。
答案 1 :(得分:3)
你是对的,大小不同,因为编译器在类LogicallyClustered中插入填充字节。编译器应该使用如下的内存布局:
class LogicallyClustered
{
// class starts well aligned
bool _fA;
// 3 bytes padding (int needs to be aligned)
int _nA;
char _cA;
bool _fB;
// 2 bytes padding (int needs to be aligned)
int _nB;
char _cB;
// 3 bytes padding (so next class object in an array would be aligned)
};
您的class TypeClustered
不需要任何填充字节,因为所有元素都是对齐的。 bool和char不需要对齐,int需要在4字节边界上对齐。
关于问题3,我会说(经常:-))“这取决于。”如果您处于一个内存占用无关紧要的环境中,我宁愿按逻辑排序以使代码更具可读性。如果您处于每个字节都很重要的环境中,您可以考虑移动成员以优化空间使用。
答案 2 :(得分:2)
除非没有极端的内存占用限制,否则将它们逻辑集中在一起,这样可以提高代码的可读性并便于维护。
答案 3 :(得分:2)
除非你确实有空间问题(即非常非常大)
有这种结构的载体),不要担心。否则:填充
添加用于对齐:在大多数计算机上,例如,double
将
在8字节边界上对齐。按照重新组合所有成员
类型,在开始时需要最多对齐的类型
导致最小的内存占用。
答案 4 :(得分:2)
Q1:是的
Q2:取决于bool的大小(取决于AFAIK编译器)。假设它是1个字节(如char),前4个成员一起使用4个字节,这与一个整数使用的一样多。因此,编译器不需要在整数前面插入对齐填充。
问题3:如果你想按类型订购,尺寸下降是一个更好的主意。但是,这种聚类会妨碍可读性。如果要在所有情况下避免填充,只需确保每个需要大于1个字节的内存的变量从对齐边界开始。
然而,对齐边界因架构而异。那是(除了可能不同的int大小)为什么相同的结构在不同的体系结构上可能具有不同的大小。通常可以安全地以a的偏移量启动每个成员x sizeof(x)的倍数。即,
struct {
char a;
char b;
char c;
int d;
}
int d将从偏移量3开始,该偏移量不是sizeof(int)的倍数(x86 / 64上为= 4),因此您应该将其移到前面。但是,没有必要严格按类型进行聚类。
某些编译器还提供了完全省略填充的可能性,例如: g ++中的__attribute((packed))__
。但是,这可能会降低程序的速度,因为int实际上可能需要两次内存访问。