DSP

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

2019-07-13 20:51发布

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)
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,编译时就会报错: 转自:http://fpcfjf.blog.163.com/blog/static/5546979320093891619457/