织梦CMS - 轻松建站从此开始!

罗索

linux64位机编程遭遇8字节对齐问题

落鹤生 发布于 2010-12-21 14:07 点击:次 
linux64位机编程遭遇8字节对齐问题,苦思不解;搜索收录几乎所有规则,对自己的问题,仍然没有合适的解释!现在,已经找到答案,有点可笑;不过是很重要的一个提醒!
TAG:

linux64位机编程遭遇8字节对齐问题,苦思不解;搜索收录几乎所有规则,对自己的问题,仍然没有合适的解释!现在,已经找到答案,有点可笑;不过是很重要的一个提醒!

规则1,关于结构体,最大的成员的大小,作为字节对齐的基准;
规则2,编译器决定字节对齐方式,我们可以选择,选择方式有三种:

a. #pragma pack(N) //改变字节对齐的基准;
#pragma pack()  //恢复默认的基准;
b. #define PACKED __attribute__((packed, aligned(4)))
typedef struct example_t
{
//...
} PACKED EXAMPLE_T;
c. gcc -fpack-struct=N ...
规则3,可以选择的字节对齐方式是2的N次方,最大值为规则1的选择;

举个例子1,

  1. struct _THUNDER{ 
  2. char cAdv; 
  3. double ca; 
  4. char cTag; 
  5. char cc; 
  6. char cd; 
  7. short cb; 
  8. int iVersion; 
  9. int iUser;   
  10. char cEnd; 
  11. }Thunder;  

sizeof(Thunder) = ?
你的答案是多少?40,
解答:
char 1
补7字节
double 8
char 1
char 1
char 1
补1字节
short 2
补2字节
int 4
int 4
char 1
补7字节  
共40字节
来自于北邮人论坛。

使用 #pragma pack(N)设置字节对齐方式,
设置8字节对齐,sizeof(Thunder) =40;
设置4字节对齐,sizeof(Thunder) =32;
设置2字节对齐,sizeof(Thunder) =26;
设置1字节对齐,sizeof(Thunder) =23;

使用 #define PACKED __attribute__((packed, aligned(4)))设置字节对齐方式,
设置8字节对齐,sizeof(Thunder) =24;
设置4字节对齐,sizeof(Thunder) =24;
设置2字节对齐,sizeof(Thunder) =24;
设置1字节对齐,sizeof(Thunder) =23;
是不是有点奇怪?!

使用# gcc -g -fpack-struct=8 test.c -o test设置字节对齐方式,
设置8字节对齐,sizeof(Thunder) =40;
设置4字节对齐,sizeof(Thunder) =32;
设置2字节对齐,sizeof(Thunder) =26;
设置1字节对齐,sizeof(Thunder) =23;

我自己的问题,我在程序中使用#pragma pack(N)设置字节对齐方式,
设置4字节对齐, 执行结果显示:结构体仍然是8字节对齐,
如果将代码分离出来,整理成一个独立的测试程序就没有问题了,真是奇哉怪也!
经过分析,认为是:可能有一个更高级别的开关,在控制!

最后,一个偶然的机会,发现其实是因为:
#pragma pack(N)  /* 4 bytes */
#pragma pack()    /* return to default */
后面的注释导致了错误的结果!
将注视去掉,问题解决!

理论文章:来自拷贝粘贴
什么是对齐,以及为什么要对齐:

现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情 况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设 为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高 低字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。

对齐的实现

通常,我们写程序的时候,不需要考虑对齐问题。编译器会替我们选择适合目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。

但是,正因为我们一般不需要关心这个问题,所以因为编辑器对数据存放做了对齐,而我们不了解的话,常常会对一些问题感到迷惑。最常见的就是struct数据结构的sizeof结果,出乎意料。为此,我们需要对对齐算法所了解。

对齐的算法:

由于各个平台和编译器的不同,现以本人使用的gcc version 3.2.2编译器(32位x86平台)为例子,来讨论编译器对struct数据结构中的各成员如何进行对齐的。
设结构体如下定义:

  1. struct A 
  2. int a; 
  3. char b; 
  4. short c; 
  5. }; 

结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个。所以A用到的空间应该是7字节。但是因为编译器要对数据成员在空间上进行对齐。
所以使用sizeof(strcut A)值为8。
现在把该结构体调整成员变量的顺序。

  1. struct B 
  2. char b; 
  3. int a; 
  4. short c; 
  5. }; 

这时候同样是总共7个字节的变量,但是sizeof(struct B)的值却是12。

下面我们使用预编译指令#progma pack (value)来告诉编译器,使用我们指定的对齐值来取代缺省的。

  1. #progma pack (2) /*指定按2字节对齐*/ 
  2. struct C 
  3. char b; 
  4. int a; 
  5. short c; 
  6. }; 
  7. #progma pack () /*取消指定对齐,恢复缺省对齐*/ 

sizeof(struct C)值是8。

修改对齐值为1:

  1. #progma pack (1) /*指定按1字节对齐*/ 
  2. struct D 
  3. char b; 
  4. int a; 
  5. short c; 
  6. }; 
  7. #progma pack () /*取消指定对齐,恢复缺省对齐*/ 

sizeof(struct D)值为7。

对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。

这里面有四个概念值:

1.数据类型自身的对齐值:就是上面交代的基本数据类型的自身对齐值。
2.指定对齐值:#progma pack (value)时的指定对齐值value。
3.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。

有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N, 就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就 是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的 整数倍,结合下面例子理解)。这样就不能理解上面的几个例子的值了。

例子分析:
分析例子B;

  1. struct B 
  2. char b; 
  3. int a; 
  4. short c; 
  5. }; 

假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认 指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为 4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐 值为2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存 放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要 求,0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到 0x000B共有12个字节,sizeof(struct B)=12;

同理,分析上面例子C:

  1. #progma pack (2) /*指定按2字节对齐*/ 
  2. struct C 
  3. char b; 
  4. int a; 
  5. short c; 
  6. }; 
  7. #progma pack () /*取消指定对齐,恢复缺省对齐*/ 

第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合 0x0000%1=0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、 0x0005四个连续字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放

在 0x0006、0x0007中,符合0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C 的有效对齐值为2。又8%2=0,C只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.

(goggle1)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201012/10628.html]
本文出处:百度博客 作者:goggle1
顶一下
(2)
100%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容