牵着老婆满街逛

严以律己,宽以待人. 三思而后行.
GMail/GTalk: yanglinbo#google.com;
MSN/Email: tx7do#yahoo.com.cn;
QQ: 3 0 3 3 9 6 9 2 0 .

字节对齐(强制对齐以及自然对齐)

转载自:http://www.cnblogs.com/alfredzzj/archive/2012/06/17/2552431.html

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个字节。

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个字节的SHORT,其小于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| 分类: vc/c/c++ | 标签: |字号大中小 订阅

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。

需要注意一点,当结构体中包含一个子结构体时,子结构中的成员按照#pragma pack指定的数值和子结构最大数据成员长度中,比较小的那个进行进行对齐。例子如下:

#pragma pack(8) 
struct s1{ 
short a; 
long b; 
};

struct s2{ 
char c; 
s1 d; 
long long e; 
}; 
#pragma pack()

sizeof(s2)的结果为24。S1的内存布局为1100 1111,S2的内存布局为1000 1100 1111 0000 1111 1111。

例子:

#include <stdio.h>
#pragma pack(2)
typedef struct
{
int aa1; //2个字节对齐 1111
char bb1;//1个字节对齐 1
short cc1;//2个字节对齐 011
char dd1; //1个字节对齐 1
} testlength1;
int length1 = sizeof(testlength1); //2个字节对齐,占用字节11 11 10 11 10,length = 10

typedef struct
{
char bb2;//1个字节对齐 1
int aa2; //2个字节对齐 01111
short cc2;//2个字节对齐 11
char dd2; //1个字节对齐 1
} testlength2;
int length2 = sizeof(testlength2); //2个字节对齐,占用字节10 11 11 11 10,length = 10


typedef struct
{
char bb3; //1个字节对齐 1
char dd3; //1个字节对齐 1
int aa3; //2个字节对齐 11 11
short cc23//2个字节对齐 11

} testlength3;
int length3 = sizeof(testlength3); //2个字节对齐,占用字节11 11 11 11,length = 8


typedef struct
{
char bb4; //1个字节对齐 1
char dd4; //1个字节对齐 1
short cc4;//2个字节对齐 11
int aa4; //2个字节对齐 11 11
} testlength4;
int length4 = sizeof(testlength4); //2个字节对齐,占用字节11 11 11 11,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;
}

另外,还有如下的一种方式:

· __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。

· __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

以上的n = 1, 2, 4, 8, 16... 第一种方式较为常见。


posted on 2014-04-17 15:43 杨粼波 阅读(996) 评论(0)  编辑 收藏 引用 所属分类: C++


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理