字节对齐
字节对齐
Andrew Huang<bluedrum@163.com>
内容提要
l 字节对齐概念
l 字节对齐测试
n offsetof
n 缺省情况的字节对齐
n double 型字节对齐
n 改变字节对齐设置
l 不同环境下的字节对齐
n GCC 字节对齐
n ADS 字节对齐
l 字节对齐练习
字节对齐概念
l 现代计算机中内存空间都是按照 byte 划分的,从理论上讲似乎对任何类型的变量的
访问可以从任何地址开始,但为了 CPU 访问数据的快速,通常都要求数据存放的地址是有一
定规律的.
l 比如在 32 位 CPU 上,一般要求变量地址都是基于 4 位,这样可以保证 CPU 用一次的
读写周期就可以读取变量.不按 4 位对齐,如果变量刚好跨 4 位编码,这样需要 CPU 两个读写
周期.效率自然低下.因此,在现代的编译器都会自动把复合数据定义按 4 位对齐,以保证 CPU
以最快速度读取,这就是字节对齐(byte Alignment)产生的背景
l 字节对齐是一种典型,以空间换时间的策略的,在现代计算机拥有较大的内存的情况,
这个策略是相当成功的.
为什么要字节对齐?
l 加快程序访问速度
l 很多 CPU 对访问地址有严格要求,这时编译器必须要这个 CPU 的规范来实现,X86
较为宽松,不对齐结构可能只影响效率,如 ARM,访问地址必须基于偶地址,MIPS 和
Sparc 也类似,这样不对齐的地址访问会造成错误.
关于字节对齐的实现
在不同的 CPU 对地址对齐有不同要求,各个编译器也会采用不同策略来实现字节对
齐,在随后的例子,可以对比 PC 下的 Windows,和 Linux,以有 ARM 下的字节对齐策略.
字节对齐带来的问题
字节对齐相当于编译器自已在开发者定义的结构里偷偷加入一些填充字符,而且各种
编译器填充的策略不一定相同.因此,在网络传输,二进制文件处理以及.底层总线传输和底层
PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn
字节对齐
数据等相关领域,忽略字节对齐会带来严重问题.这样会产生错位使用程序处理数据完全错误.
因此,网络以及硬件相关开发人员必须对字节对齐要有清晰的了解.
字节对齐测试
操作符
offsetof
在分析字节对齐之前,首先了解一下 offsetof 宏.这个宏是标准 C 的定义,每个 C 库均会在
stddef.h 中定义.作用是计算结构或联合每一个成员的偏移量.用 offsetof 我们可以很清晰看到
字节是如何对齐的.
它的用法如下:
typedef struct { char c1; int i1; char c2; } S3;
printf(“c1 offset=%d,i1 offset =%d,c2 offset=%d\n”,
offsetof(S3,c1),offsetof(S3,i1),offsetof(S3,c2));
offsetof 在不同操作系统下定成不同形式.
/* Keil 8051 */
#define offsetof(s,m) (size_t)&(((s *)0)->m)
/* Microsoft x86 */
#ifdef _WIN64
#define offsetof(s,m) (size_t)( (ptrdiff_t)&( ( (s *)0 )->m ) )
#else
#define offsetof(s,m) (size_t)&( ( (s *) 0 )->m )
#endif
/* Motorola coldfire */
#define offsetof(s,memb) ((size_t)((char *)&((s *)0)->memb-(char *)0))
/* GNU GCC 4.0.2 */
#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
注意:offsetof 不能求位域成员的偏移量,offsetof 虽然引用了一个空指针来操作成员,但是
由于只是在取类型,并且这个值在编译期就被确定,所以编译器在编译会直接算出 offsetof 的
值,而不会在运行期引起内存段错误.
以下我们用 offsetof 来分析结构和字节对齐
缺省情况的字节对齐
缺省的情况我们是指 32Bit CPU,Windows 使用 VC++ 6.0,用这个环境基本能说明问题,其
余的环境有不同的,再补充说明.
对齐有如下情况:
1. 基本类型变量起始地址要按一定规则对齐.
PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn
字节对齐
l char 类型,其起始地址要 1 字节边界上,即其地址能被 1 整除(即任意地址即可)
l short 类型,其起始地址要 2 字节边界上,即其地址能被 2 整除
l int 类型,其起始地址要 4 字节边界上,即其地址能被 4 整除
l long 类型,其起始地址要 4 字节边界上,即其地址能被 4 整除
l float 类型,其起始地址要 4 字节边界上,即其地址能被 4 整除
l double 类型,其起始地址要 8 字节边界上,即其地址能被 8 整除
2. 结构实例起始址要在自己最大尺寸成员的对齐地址上
如最大尺寸的成员是 short,则要基于 2 对齐
3. 结构内成员的偏移量也要参照第 1 条,满足相应倍数
如成员是 short,则偏移量也是 2 的倍数.
这一条实际仍然是第 1 条规则的扩展,因为结构起始地址按最大倍数来,加上内部相
应倍数,这样成员绝对地址仍然满足第 1 条规定
4. 结构总尺寸也要对齐. 要为最大尺寸的成员的整数倍,
如果不是则要在结构最后补齐成整数倍
关于第一条,我们做如下测试
{
char c1;
int i1;
short o1;
double d1;
#define ADDR_DIFF(a,b) ((char*)a) - ((char *)b)
printf("c1 addr=0x%x,i1 addr=0x%x,o1 addr=0x%x,d1 addr=0x%x\n",
&c1,&i1,&o1,&d1);
printf("c1-i1 =%d,i1-o1=%d,o1-d1=%d\n",
ADDR_DIFF(&c1,&i1),ADDR_DIFF(&i1,&o1),ADDR_DIFF(&o1,&d1));
}
Win32 下测试结果:
c1 addr=0x12ff7c, i1 addr=0x12ff78,o1 addr=0x12ff74,d1 addr=0x12ff6c
c1-i1 =4,i1-o1=4,o1-d1=8
从测试结果可以看出,编译器并没有紧密的把各个数据结构排列在一起,而是按其对齐地
址进行分配
结构的字节对齐
例 1:
typedef struct s2{
int a;
PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn
字节对齐
short b;
char c;
}s2;
printf("s2 size=%d,int a=%d,short b=%d,char c=%d\n",
sizeof(s2),offsetof(s2,a),offsetof(s2,b),offsetof(s2,c));
测试结果是 s2 size=8,int a=0,short b=4,char c=6
从结果看.是总尺寸是 8,各成员尺寸之和是 7,从偏移量可以看在最后补齐一个字符,这是
按规则 4,总尺寸是最大成员倍数
例 2:
typedef struct s5{
int a;
char b;
short c;
}s5;
printf("s5 size=%d,int a=%d,char b=%d,short c=%d\n",
sizeof(s5),offsetof(s5,a),offsetof(s5,b),offsetof(s5,c));
测试结果是 s5 size=8,int a=0,char b=4,short c=6
这一次补齐的目的是为了 short 型的 c 基于 2 对齐,应用第 3 条规则
例 3:
PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn
字节对齐
typedef struct s10{
char b;
int a;
short c;
}s10;
printf("s10 size=%d,char b=%d,int a=%d,short c=%d\n",
sizeof(s10),offsetof(s10,b),offsetof(s10,a),offsetof(s10,c));
测试结果: s10 size=12,char b=0,int a=4,short c=8
第一次补齐的目的是为了 int 型的 a 基于 4 对齐,应用第 3 条规则
第二次补齐为了合符第 4 条规则.要为 int 的倍数.
例 5:
typedef struct s4{
char a;
short b;
char c;
}s4;
printf("s4 size=%d,int a=%d,short b=%d,char c=%d\n",
sizeof(s4),offsetof(s4,a),offsetof(s4,b),offsetof(s4,c));
测试结果: s4 size=6,int a=0,short b=2,char c=4
这里最大尺寸的成员是 short b 所以总尺寸是 2 的倍数,而且 short 本身也需要 2 对齐,因此在
两个不同地方补了一个 byte
double 型的字节对齐
先看测试样例
typedef struct s1{
PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn
字节对齐
char a;
double b;
short c;
}s1;
printf("s1 size=%d,char a=%d,double b=%d,short c=%d\n",
sizeof(s1),offsetof(s1,a),offsetof(s1,b),offsetof(s1,c));
在 Windows +VC 6.0 下测试结果: s1 size=24,char a=0,double b=8,short c=16
在 Redhat 9.0 +gcc 3.2.2 下测试结果: s1 size=16,char a=0,double b=4,short c=12
可以看到在两个编译器上,对 double 的对齐处理不一样.在 Linux 下,double 采用是基于 4 对
齐.而 Windows 采用 8 对齐.
再看一个实例
PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn
字节对齐
typedef struct s1{
char a;
double b;
char c;
int d;
}s1;
printf("s6 size=%d,char a=%d,double b=%d,char c=%d int d=%d\n",
sizeof(s6),offsetof(s6,a),offsetof(s6,b),offsetof(s6,c),offsetof(s6,d));
在 Windows +VC 6.0 下测试结果: s6 size=24,char a=0,double b=8,char c=16 int d=20
在 Redhat 9.0 +gcc 3.2.2 下测试结果: s6 size=20,char a=0,double b=4,char c=12 int d=16
改变字节对齐设置
默认的字节对齐都是按最大成员尺寸来进行对齐,但是在开发中可能需要调整对齐宽度.
最常的一种情况是,在在网络和底层传输中取消字节对齐,完成按原始尺寸紧密的排列.
还有一种情况是扩大或缩少字节对齐的排列.这种情况比较复杂.但应用比较少.
取消字节对齐
在文件处理,网络和底层传输中,数据都是紧密排列.不希望编译器在结构内部自行增加
空间.这时需要开发者通知编译器,某一些结构是不需要字节对齐的.
绝大部分编译器是使用预编译指令 pragma 取消对齐
l #pragma pack (n) 设置对齐宽度为 n,它可以是 1,2,4,8 等等,其中 1 就表示不进行字
节对齐.
n # pragma pack (n)是成片生效的,即在这个指令后面所有结构都会按新的对齐值
进行对齐
l # pragma pack() 将上一次# pragma pack (n)的设置取消.恢复为默认值.
l 两者是成对使用,在这两者之间所有结构均受到影响
注意是 pragma,不是 progma
例子:
#pragma pack(1)
typedef struct s7{
int a;
short b;
char c;
}s7;
#pragma pack()
printf("s7 size=%d,int a=%d,short b=%d,char c=%d\n",
sizeof(s7),offsetof(s7,a),offsetof(s7,b),offsetof(s7,c));
测试结果 s7 size=7,int a=0,short b=4,char c=6
PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn
字节对齐
可以看到,取消字节对齐,sizeof()就成员尺寸之和.
改变字节对齐
这种情况比较复杂,而且也不常用.也是通过#pragma pack(n)来完成生效,但是要注意,
字节对齐值采用 n 和默认对齐值中较小的一个.换句话说,扩大对齐值是不生效的.
#pragma pack 还有其它功能
l #pragma pack(push) // 将当前 pack 设置压栈保存
l #pragma pack(pop) // 恢复先前的 pack 设置
这两个功能用于多种对齐值混用的场合,(当然,这种情况也是非常少见)
缩小例子:
#pragma pack (2) /*指定按 2 字节对齐,缺省是 4 */
typedef struct s8
{
char a;
int b;
short c;
}s8;
#pragma pack ()
printf("s8 size=%d,char a=%d,int b=%d,short c=%d\n",
sizeof(s8),offsetof(s8,a),offsetof(s8,b),offsetof(s8,c));
测试结果: s8 size=8,char a=0,int b=2,short c=6
缺省的 4 字节对齐话,sizoef 应该是 12,现在改为 2 对齐的话,只能是 8,即在 char a 补了一
个字节.
扩大的例子:
#pragma pack (8) /*指定按 2 字节对齐,缺省是 4 */
typedef struct s9
{
char a;
int b;
short c;
}s9;
#pragma pack ()
printf("s9 size=%d,char a=%d,int b=%d,short c=%d\n",
sizeof(s9),offsetof(s9,a),offsetof(s9,b),offsetof(s9,c));
测试结果:s9 size=12,char a=0,int b=4,short c=8
这个结果跟 4 对齐是一样的,换句话说,8 对齐没有生效
PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn
字节对齐
不同环境下的字节对齐使用
GCC 的字节对齐控制
GCC 也支持#pragma 字节控制
l #pragma pack (n),gcc 将按照 n 个字节对齐
l #pragma pack (),取消自定义字节对齐方式
#pragma 只保证的成员相关偏移量是字节对齐的.不保证绝对地址对齐.
GCC 也支持某个一个数据结构实现绝对地址的自然对齐
__attribute((aligned (n))) 让所作用的结构成员对齐在 n 字节自然边界上。如果结构中有
成员的长度大于 n,则按照最大成员的长度来对齐。
__attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行
对齐。
struct STRUCT_TEST
{
char a;
int b;
char c;
} __attribute__ ((packed)); //注意位置,在}与;之间
l __attribute 是 GCC 属性,跟#pragma 不同, __attribute__是 gcc 的方言,只有 GCC 能
识别,不要在 VC++之类编译器使用这种定义.
l __attribute 每次只对一个结构生效.
ADS 的字节对齐控制
ARM 对访问地址有特殊要求,如果不对齐,会造成程序错误,而不是象 X86 或 PowerPC
那样折成两个指令访问. 因此用#pragma pack(1) 只是让结构本身成员内部按 1 对齐,并不
能保证结构的绝对地址是对齐.
ADS 采 用 特 殊 指 令 来 实 现 要 想 保 证 地 址 对 齐 .ADS 采 用
ALIGN.__align(num), .__packed,来控制字节对齐
l ALIGN 用于汇编的字节对齐控制
l __align(num) 类似于#pragma pack(num),用于整片代码字节对齐的的控制.
PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn
字节对齐
l __packed 取消某个结构或成员的内部字节对齐,并实现绝对地址对齐,类似于 gcc 的
__attribute__ ((packed));
__packed struct STRUCT_TEST
{
char a;
int b;
char c;
};
字节对齐练习
请指在 windows 32 下出下列值的 sizeof 和各个成员的偏移量
1. struct s1{
short a;
short b;
short c;
};
2. struct s2{
char a[21];
short b;
};
3. struct s2{
float a;
char b;
short c;
int d;
};
5. #pragma pack (1)
typedef struct s8
{
char a;
int b;
short c;
double d;
}s8;
#pragma pack ()
PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn
myroom 于 2010-02-28 13:57:13发表:
看不懂。。
zhangbohtz 于 2010-02-03 12:40:16发表:
请高手指点一二,主要是关于网络传输,和程序移植方面会特别关注吗?
arp是31B的?能讲讲好吗?