字节对齐(强制对齐以及⾃然对齐)
struct {}node;
32为的x86,window下VC下sizeof(node)的值为1,⽽linux的gcc下值为0;
⼀、WINDOWS下(VC--其实GCC和其原理基本⼀样,象这种问题,⼀般要查具体的编译器设置)字节对齐的规则:
1、⼀般设置的对齐⽅式为1,2,4字节对齐⽅式,VC⼀般默认为4字节(最⼤为8字节)。结构的⾸地址必须是结构内最宽类型的整数倍地址;另外,结构体的每⼀个成员起始地址必须是⾃⾝类型⼤⼩的整数倍(需要特别注意的是windows下是这样的,但在linux的gcc编译器下最⾼为4字节对齐),否则在前⼀类型后补0;这⾥特别提到的是数组⼀定要注意,⽽且在⼀些编程的技巧中,我们可以使⽤数组强制字节达到对齐的⽬的。这在⽹络编程中是很常见的。
举例:⽐如CHAR型占⽤空间为1字节,则其起始位置必须可被1整除。INT为4字节,其起始位置必须被4带队,依次类推。(我们假定类或结构体的起始位置为0位置,其实编译器是在开辟空间时,会寻起始位置可被结构内最宽类型整除的地址做为开始地址,因此我们可以假定其为0值,因为这0值可以被任意的类型整除。)
2、结构体的整体⼤⼩必须可被对齐值整除,默认4(默认,且结构中的类型⼤⼩都⼩于默认的4)。
3、结构体的整体⼤⼩必须可被本结构内的最宽类型整除。(其实和上⼀条是⼀样的,但这⾥独⽴出来,起注意作⽤。⽐如结构体⾥的有DOUBLE,那么结构的⼤⼩最后必须可被8整除)
注意:GCC不是这样,就是最⾼只能被4整除,它是个死的。
否则(2、3条),编译器会在结构的最后添充⼀定的特定字符来补齐。
struct T
{
char ch;
double d ;
};
在VC中是16个字节,GCC中为12个字节。
sizeof结构体大小
4、对于结构体内嵌套结构体的形势,规定是必须按照基本数据类型来定义,⽽不能以嵌套结构⼤⼩来做为上三种使⽤的基准。
⼆、举例:
struct A
{
int a;
char b;
short c;
};
struct B
{
char b;
int a;
short c;
};
struct C
{
double t;
char b;
int a;
short c;
};
struct D
{
char b;
double t;
int a;
short c;
};
在VC中,SIZEOF这四个结构体,分别为:8、12、24、24;
我们先谈第⼀个,(说明⼀下,在考虑结构体⼤⼩时,我们基本可以忽略起始地址的问题,因为这个编译器会⾃动为我们做好,见上⾯的说
明),结构体内⾸先是⼀个INT的4字节,起始地址假定为0,整除4,其⼩于等于默认的4字节对齐且0为4(INT的占⽤空间)的整数倍,所以,其占四个字节;其后为起始地址为5,空间为1个字节的CHAR,⼩于4且5为1(CHAR占⽤空间)的整数倍,故占⽤1个字节,然后是⼀个起始地址为5占2个字节的SHO
RT,其⼩于4,但5不为2的整数倍,故补齐⼀个字节,从第6个字节开始,占2字节空间。所以共占⽤
4+1+1(补)+2=8;8/4=2;整除,故占⽤8字节空间。
再谈第2个,CHAR不⽤解释,占有⼀个字节空间,且可以被0地址整除。⽽INT则占4字节空间,所以其必须在CHAR后补齐3字节,到第四个字节,才是INT的真正地址。SHORT也不⽤说,所以共占有:1+3(补)+4+2=10个字节,但10不能整除4,所以要在结构体最后补齐2字节。故实际占有10+2= 12个字节。
谈第三个,C结构体只是在B结构体前加了⼀个DOUBLE,其它都⼀样,按说应该是20个字节啊,但注意我们上⾯规则的第3条。必须是最宽类型的整数倍,⼀定要分清,所以得补齐到24,D结构体类似,不再讲。
三、结构体的中含有位域
这个东西⽤得⽐较少,但还是总结⼀下:
如果结构体中含有位域(bit-field),那么VC中准则⼜要有所更改:
1) 如果相邻位域字段的类型相同,且其位宽之和⼩于类型的sizeof⼤⼩,则后⾯的字段将紧邻前⼀个字段存储,直到不能容纳为⽌;
2) 如果相邻位域字段的类型相同,但其位宽之和⼤于类型的sizeof⼤⼩,则后⾯的字段将从新的存储单元开始,其偏移量为其类型⼤⼩的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩⽅式(不同位域字段存放在不同的位域类型字节
中),Dev-C++和GCC都采取压缩⽅式;
备注:当两字段类型不⼀样的时候,对于不压缩⽅式,例如:
struct N
{
char c:2;
int i:4;
};
依然要满⾜不含位域结构体内存对齐准则第2条,i成员相对于结构体⾸地址的偏移应该是4的整数倍,所
以c成员后要填充3个字节,然后再开辟4个字节的空间作为int型,其中4位⽤来存放i,所以上⾯结构体在VC中所占空间为8个字节;⽽对于采⽤压缩⽅式的编译器来说,遵循不含位域结构体内存对齐准则第2条,不同的是,如果填充的3个字节能容纳后⾯成员的位,则压缩到填充字节中,不能容纳,则要单独开辟空间,所以上⾯结构体N在GCC或者Dev-C++中所占空间应该是4个字节。
4) 如果位域字段之间穿插着⾮位域字段,则不进⾏压缩;
备注:
结构体
typedef struct
{
char c:2;
double i;
int c2:4;
}N3;
在GCC下占据的空间为16字节,在VC下占据的空间应该是24个字节。
四、字节对齐的控制⽅法
主要是使⽤:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
⼤家如果有兴趣,可以⾃⼰上机调⼀下各种对齐⽅式下的占⽤空间⼤⼩,这⾥就不再举例。
#pragma pack(push) //保存对齐状态
#pragma pack(4)//设定为4字节对齐
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢复对齐状态
这⾥需要注意的是,如果对齐的字节⾮为1、2、4、8等可整除位数,则⾃动默认回默认的对齐字节数,这个我没有测试,⼤家可以试⼀下,应该没什么问题。
五、多编译器的使⽤:(其下为转载)
为了防⽌不同编译器对齐不⼀样,建议在代码⾥⾯指定对齐参数
可能重要的⼀点是关于紧缩结构的。紧缩结构的⽤途其实最常⽤的结构对齐选项就是:默认对齐和紧缩。在两个程序,或者两个平台之间传递数据时,我们通常会将数据结构设置为紧缩的。这样不仅可以减⼩通信量,还可以避免对齐带来的⿇烦。假设甲⼄双⽅进⾏跨平台通信,甲⽅使⽤了“/Zp2”这么奇怪的对齐选项,⽽⼄⽅的编译器不⽀持这种对齐⽅式,那么⼄⽅就可以理解什么叫欲哭⽆泪了。当我们需要⼀个字节⼀个字节访问结构数据时,我们通常都会希望结构是紧缩的,这样就不必考虑哪个字节是填充字节了。我们把数据保存到⾮易失设备时,通常也会采⽤紧缩结构,既减⼩存储量,也⽅便其它程序读出。各编译器都⽀持结构的紧缩,即连续排列结构的各成员变量,各成员变量之间没有任何填充字节。这时,结构的⼤⼩等于各成员变量⼤⼩的和。紧缩结构的变量可以放在1n边界,即任意地址边界。在GNU gcc:
typedef struct St2Tag
{
St1 st1;
char ch2;
}
__attribute__ ((packed)) St2;
在ARMCC:
typedef __packed struct St2Tag
{
St1 st1;
char ch2;
} St2;
在VC:
#pragma pack(1)
typedef struct St2Tag
{
St1 st1;
char ch2;
} St2;
#pragma pack()
针对不同的编译器:
#ifdef __GNUC__
#define GNUC_PACKED __attribute__ ((packed))
#else
#define GNUC_PACKED
#endif
#ifdef __arm
#define ARM_PACKED __packed
#else
#define ARM_PACKED
#endif
#ifdef WIN32
#pragma pack(1)
#endif
typedef ARM_PACKED struct St2Tag
{
St1 st1;
char ch2;
}
GNUC_PACKED St2;
#ifdef WIN32
#pragma pack()
#endif
最后记录⼀个⼩细节。gcc编译器和VC编译器都⽀持在紧缩结构中包含⾮紧缩结构,例如前⾯例⼦中的St2可以包含⾮紧缩的St1。但对于ARM编译器⽽⾔,紧缩结构包含的其它结构必须是紧缩的。如果紧缩的St2包含了⾮紧缩的St1,编译时就会报错:
C语⾔的字节对齐及#pragma pack的使⽤
2010-04-16 09:44:33| 分类: | 标签: |字号⼤中⼩
C编译器的缺省字节对齐⽅式(⾃然对界)
在缺省情况下,C编译器为每⼀个变量或是数据单元按其⾃然对界条件分配空间。
在结构中,编译器为结构的每个成员按其⾃然对界(alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储(成员之间可能有插⼊的空字节),第⼀个成员的地址和整个结构的地址相同。
C编译器缺省的结构成员⾃然对界条件为“N字节对齐”,N即该成员数据类型的长度。如int型成员的⾃然对界条件为4字节对齐,⽽double类型的结构成员的⾃然对界条件为8字节对齐。若该成员的起始偏移不位于该成员的“默认⾃然对界条件”上,则在前⼀个节⾯后⾯添加适当个数的空字节。
C编译器缺省的结构整体的⾃然对界条件为:该结构所有成员中要求的最⼤⾃然对界条件。若结构体各成员长度之和不为“结构整体⾃然对界条件的整数倍,则在最后⼀个成员后填充空字节。
例⼦1(分析结构各成员的默认字节对界条界条件和结构整体的默认字节对界条件):
struct Test
{
char x1; // 成员x1为char型(其起始地址必须1字节对界),其偏移地址为0
char x2; // 成员x2为char型(其起始地址必须1字节对界,其偏移地址为1
float x3; // 成员x3为float型(其起始地址必须4字节对界),编译器在x2和x3之间填充了两个空字节,其偏移地址为4
char x4; // 成员x4为char型(其起始地址必须1字节对界),其偏移地址为8
};
因为Test结构体中,最⼤的成员为flaot x3,因些此结构体的⾃然对界条件为4字节对齐。则结构体长度就为12字节,内存布局为1100 1111 1000。
例⼦2:
#include <stdio.h>
//#pragma pack(2)
typedef struct
{
int aa1; //4个字节对齐 1111
char bb1;//1个字节对齐 1
short cc1;//2个字节对齐 011
char dd1; //1个字节对齐 1
} testlength1;
int length1 = sizeof(testlength1); //4个字节对齐,占⽤字节1111 1011 1000,length = 12
typedef struct
{
char bb2;//1个字节对齐 1
int aa2; //4个字节对齐 01111
short cc2;//2个字节对齐 11
char dd2; //1个字节对齐 1
} testlength2;
int length2 = sizeof(testlength2); //4个字节对齐,占⽤字节1011 1111 1000,length = 12
typedef struct
{
char bb3; //1个字节对齐 1
char dd3; //1个字节对齐 1
int aa3; //4个字节对齐 001111
short cc23//2个字节对齐 11
} testlength3;
int length3 = sizeof(testlength3); //4个字节对齐,占⽤字节1100 1111 1100,length = 12
typedef struct
{
char bb4; //1个字节对齐 1
char dd4; //1个字节对齐 1
short cc4;//2个字节对齐 11
int aa4; //4个字节对齐 1111
} testlength4;
int length4 = sizeof(testlength4); //4个字节对齐,占⽤字节1111 1111,length = 8
int main(void)
{
printf("length1 = %d.\n",length1);
printf("length2 = %d.\n",length2);
printf("length3 = %d.\n",length3);
printf("length4 = %d.\n",length4);
return 0;
}
改变缺省的对界条件(指定对界)
· 使⽤伪指令#pragma pack (n),C编译器将按照n个字节对齐。
· 使⽤伪指令#pragma pack (),取消⾃定义字节对齐⽅式。
这时,对齐规则为:
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第⼀个数据成员放在offset为0的地⽅,以后每个数据成员的对齐按照
#pragma pack指定的数值和这个数据成员⾃⾝长度中,⽐较⼩的那个进⾏。
2、结构(或联合)的整体对齐规则:在数据成员完成各⾃对齐之后,结构(或联合)本⾝也要进⾏对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最⼤数据成员长度中,⽐较⼩的那个进⾏。
结合1、2推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的⼤⼩将不产⽣任何效果。
因此,当使⽤伪指令#pragma pack (2)时,Test结构体的⼤⼩为8,内存布局为11 11 11 10。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论