Linuxer
人生的乐趣有多种:爱、享受、冒险等等,还有发现和创造。能享受后两种乐趣的人并不多。做事应该带着发现的心态而不是争强好胜的心态。

导航

 

从一个例子开始

 象下面这样定义的结构体占几个字节?

typedef struct{
    char a;
    int i;
} Sample;

 

char占1个字节,int占4个字节,答案是5个字节? 错了。如果用 gcc 编译,sizeof(Sample) 的结果是8个字节。

这是怎么回事?进一步观察,Sample第0个字节是a, 而Sample的成员int i从第4个字节开始。 这是编译器为了内存对齐所做的优化。

 

什么是内存对齐

 

比如数据总线有32位,它访存只能4个字节4个字节地进行。 0-3,4-7,8-11,12-15,…… 即使我们需要的数据只占一个字节,也是一次读取4个字节。 一个字节的数据不管地址是什么,都能通过一次访存读取出来。 而如果要读取的数据是一个字节以上,比如两个字节, 如果该数据的内存地址是0x03,则需要两次才能读取该数据, 第一次读0x00-0x03,第二次读0x04-0x07。 这个数据就跨越了访存边界。

而相对CPU的运算来说,访存是非常慢的,所以要尽量减少访存次数。 为了减少跨越访存边界的数据引起的访存开销, 所以编译器会进行内存对齐,即把变量的地址做一些偏移, 目的是一次访存就读出数据,不然的话也要以尽可能少地访存次数读出数据。

如上一个例子中那样,整型成员i的地址做4个字节的偏移, 而Sample对象的地址也会做4字节边界的对齐, 这样i的地址始终是4的倍数,从而使得i不跨越访存边界, 能一次读出它的值。

尽可能少的内存占用

typedef struct{
    char a;
    char b;
    int i;
} Sample1;

 

Sample1占多少空间呢?仍然是8个字节。 a在第0个字节,b在第1个字节,i占4-7字节。 这是内存对齐的原则,占用尽量少的内存。 如果在b之后,还有char类型的成员c和d,同样是占8个字节。 a,b,c,d在0-3字节。

 

数据成员顺序影响内存的占用

typedef struct{
    char a;
    int i;
    char b;
} Sample2;
Sample2的数据成员和Sample1的数据成员相同,只是顺序不一样, 是否占用一样多的内存呢?答案是不一样。 Sample1占用8字节,而Sample2占用12个字节!

这是为什么呢?32位机器上,访存单元是4字节。 对于多于4字节的数据,以4字节为单位做内存对齐的。 所以对于上述的结构体数据的地址分配,都是4的倍数。 对于结构体内部,以数据成员的最大长度为对齐单位。 如果数据成员的长度超过4字节,仍然以4字节为对齐单位,这是因为访存单元就是4字节。 在Sample2的定义中,因为int是4字节,所以以4字节为内存对齐的单位。

在Sample2中,对于a来说,只占一个字节,无所谓对不对齐,它在0字节。 i是int类型,要对齐,在4-7字节。 b在8字节,虽然它只占一个字节,但因为结构体内是以4字节为单位对齐的, 所以编译器一下多分配出4个字节,所以共占了12个字节。

以上的Sample1和Sample2的例子说明,即使是同样的结构体,如果数据成员的顺序不同,所占的内存空间可能是不同的。

 

总结

  • 如果我们对内存对齐有概念,在定义结构体的时候就会留心数据成员的顺序,从而能 减少程序的内存占用。

  • 以前我使用变量存放较小的整数时,为了减小内存占用,往往把变量定义成char型, 但现在明白了,因为编译器的内存对齐,节省内存的效果并没有那么理想。

  • 如果内存真的很受限,可以用 #pragma pack(1) 告诉编译器关闭内存对齐。

posted on 2014-10-22 15:40  JollyWing  阅读(1321)  评论(0编辑  收藏  举报