1、位域对齐
3.7版本之后GCC都默认使用了-mms-bitfields,此选项意义为使用Microsoft的方式进行对齐操作,其对齐策略为将对所有类型相同的位域合并到一起。与之相对的是GCC对其方式,其对齐策略为将所有位域合并到一起,并不区分位域类型。
如下例:
[cpp]
struct {
unsigned long long c : 1;
unsigned int a : 1;
unsigned int b : 1;
}
Mircrosoft对齐方式将合并类型相同的a、b,为之分配8个字节,而对于c,则单独分配8个字节,所以上述结构体在此对齐方式下大小为16.
GCC对齐方式将不区分类型合并所有的位域,并根据原类型中最大尺寸分配位域保存所需的字节数。譬如对于上述结构,最大尺寸为long long,所以上述结构体大小为8.
此结论可以通过为struct添加不同的GCC结构属性测试得到。
[cpp]
struct fields {
unsigned long long c :1;
unsigned int a :1;
unsigned int b :1;
//}__attribute__ ((__ms_struct__)); //用于测试Microsoft对齐方式。得到sizeof(fields)结果为16。
//}__attribute__ ((__gcc_struct__));//用于测试GCC对齐方式。得到sizeof(fields)结果为8。
从gcc与ms(Microsoft)的对齐策略可见,gcc的对齐方式相对更加节约存储空间。
2、结构对齐
先看一个结构体:
[cpp]
struct struct_ {
char first[1];
long int second;
char third[1];
};
此结构体使用默认的GCC选项编译后,既默认使用-mms-bitfields选项的情况下,将得到大小为9字节的存储空间。
这是因为对于32位操作系统,GCC默认对齐字节为4字节,在使用MS结构对齐策略的情况下,GCC在处理第一个变量时保持其尺寸不变,既为1字节,在处理第二个变量时同样保持其尺寸不变,既为4字节,在处理第三个也是最后一个变量时,扩展其尺寸至对齐尺寸4字节。因此最后得到的总结构大小为1+4+4=9字节。
总而言之,MS的对齐策略仅对结构中最后一个变量起作用。
如果关闭默认的MS对齐策略(通过为GCC添加-mno-ms-bitfields参数),转而使用GCC的对齐策略,上述结构的尺寸将会是多少?
测试的结果是:12。为何?这是因为GCC将对齐策略应用在了结构体中的每个变量上,对于非4字节的整数倍的char类型变量first[1]、third[2],GCC都将其扩展为4个字节,对于second,由于其类型大小为4字节,所以GCC保持其大小不变。扩展完成后,结构体的大小为:4+4+4=12字节。
3、强制设定对齐方式
字节对齐有利于整块读取数据,提高数据吞吐量,但是这是在牺牲存空间的情况下得到的,而在实际应用中,比如网络环境下,为了减少数据传输量,我们并不希望使用字节对齐方式,这时需要关闭字节对齐。
在程序的移植过程中,比如将32bit系统下的程序移植到64bit系统下,原有的字节对齐方式可能无法达到提高数据吞吐的目的,因此,我们需要更改字节对齐方式。
如何关闭?如何更改?
GCC提供了几种方式:
3.1、通过属性方式改变:
__attribute__ ((__ms_struct__)) 指定使用MS对齐策略
__attribute__ ((__gcc_struct__)) 指定使用GCC对齐策略
__attribute__ ((__packed__)) 指定使用最少存储空间对齐策略:合并所有位、对变量使用单字节方式对齐。
3.2、通过标识符声明方式:
#pragma pack(N) 设定对齐方式为N字节。
#pragma pack() 设定对齐方式为上一次对齐方式。
#pragma pack(push[, n]) 保存当前对齐方式并设置对齐方式为N字节。
#pragma pack(pop) 恢复最近一次保存的对齐方式。
对于i386构架,GCC专门提供了如下3个声明控制对齐方式:
#pragma ms_struct on 启用MS对齐方式。
#pragma ms_struct off 关闭MS对齐方式。
#pragma ms_struct reset 重置当前对齐方式为默认对齐方式。
3.3 通过设置GCC编译参数方式:
-fpack-struct[=N] 设定对齐方式为N字节对齐,不带”=N“时默认为4字节对齐。
-mno-ms-bitfields 关闭MS对齐策略,使用GCC对齐策略。