说说C语言和内存对齐
Monday, November 17th, 2008 154 views我看东西,看得快,忘得也奇快。大概2星期重新研究过内存对齐,现在都快忘光了,于是赶紧再回忆一下。
![]()
所谓内存对齐,即使得数据所在内存地址的起点应尽量对齐(是某个值的公倍数),而不是凌乱不堪的。一般我们把8个bit看成1一个byte,用byte来做存储的单位,其实也是一种内存对齐的机制——这样处理器访问数据只需要访问8的倍数即可。
内存对齐的目的很简单,这样有利于提供访问的速度(特别是在栈中)。对于对齐的内存地址,处理器访问一次即可得到,而未对齐的地址,可能需要访问两次才行。
那内存对齐的最小单位是什么呢?其实不同平台上不同的编译器都有自己的决定(猜的),编译器的这个决定叫做默认对齐系数。我们在自己编写程序的时候很少会去注意到这个对齐问题,这个很正常,因为这机制本身就应该是对程序员透明的。但是,我们还是能够在程序中觉察到对齐机制的存在。如果程序员没有正确意识到对齐机制的存在,有的时候它甚至会让程序的行为不正确。我举个小小的例子来说明下。
假设有这样一个结构体
typedef struct ex{ int c1; char c2; int c3; }ve; /* 1 */
我的运行环境是gcc4.0+, ubuntu8.04, 32位单核cpu,sizeof(char)是1,sizeof(int)是4,猜猜sizeof(ve)的值会是多少?如果程序员不清楚内存对齐的概念的话会认为这个值是6,但其实是12。在这里面,有4个成员需要对齐,c1,c2,c3,ve,c1,c2,c3跟ve的对齐规则并不一致。
c1,c2,c3的对齐过程如下(对齐系数是4的前提下):假设第一个成员放在offset为0的位置,那么c1所占的内存就是[0~3],而c2所占的内存是[4],c3所占的内存不是[5~8],而是[8~12],但如果c3是char类型的话,c3所占的内存就是[5]。
所以结构体内成员的对齐规则如下:假设第一个成员的offset是0,则之后成员的对齐系数是系统的对齐系数(可以通过pragma pack(x)来指定)和自身长度两者间较小的值。比如,系统的对齐系数是4,而char的长度是1,那么char就按1对齐。而int的长度是4,于系统对齐系数一致,则按4对齐。
从上面这个规则,我们可以看到一个有趣的问题,在结构体内,成员的摆放顺序不同,结构体所占空间是不同的。比如,
typedef struct ex{ char c1; char c2; int c3; }ve; /* 2 */
与
typedef struct ex{ char c1; int c3; char c2; }ve; /* 3 */
,前者所占的空间是8,而后者所占空间是12。但是对于程序的逻辑而言,两者的定义是完全一致的。了解对齐规则是有好处的,至少我们写出来的结构体空间占用更小些。
说完了c1,c2,c3的对齐,下面说ve的对齐。sizeof(ve)的值事实上并不是如我们分析的c1,c2,c3对齐完之后所占空间的累加,它自身也是需要对齐的。结构体的对齐规则如下:取其成员中占用空间最长的值,我们的例子中是int类型,值是4,然后跟系统的对齐系数比较,取较小值为ve的对齐系数。如果我们在结构体2中,语句int c3;后面再加一句char temp;我们可以算出成员所占的空间是9,而ve所占的空间是12。