群集成员变量声明的类型是否有用?

时间:2012-01-13 13:07:32

标签: c++

请查看以下代码示例,使用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;
}

问题1

这两个类的sizeof不同,因为编译器正在插入填充字节以实现变量的优化内存对齐。这是对的吗?

问题2

如果我按照class TypeClustered中的类型对变量进行聚类,为什么内存占用量会减小?

问题3

总是根据类型对成员变量进行聚类是一个很好的经验法则吗? 我是否应该根据它们的大小升序(bool,char,int,double ......)对它们进行排序?

修改

附加问题4

较小的内存占用将提高数据缓存效率,因为可以缓存更多对象,并避免完全内存访问“慢速”RAM。那么成员声明的排序和分组是否可以被视为(小)但易于实现的性能优化?

5 个答案:

答案 0 :(得分:5)

1)绝对正确。

2)它不小,因为它们是分组的,但是因为它们有序和分组的方式。例如,如果一个接一个地声明4 chars,则它们可以打包成4个字节。如果您声明一个char并且立即声明一个int,则将插入3个填充字节,因为int将需要与4个字节对齐。

3)不!您应该在一个类中对成员进行分组,以使该类变得更具可读性。

重要提示:这是所有特定于平台/编译器的。不要把它带到ad-literam。

另一个注意事项 - 在某些平台上也存在一些小的性能提升,用于访问驻留在类实例的第一个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实际上可能需要两次内存访问。