C++中字节对齐分析(有例子)

本文转载自Repository的博客https://www.cnblogs.com/repository/archive/2011/01/13/1933721.html,对原作的基础上,本文增加了几个例子和一点自己的分析,作相关的保存,以便日后查阅。

 

 

  1.示例代码   

  先看一下这段程序的运行结果。

struct A
{
    int a;
    char b;
    short c;
};

struct B
{
    char a;
    int b;
    short c;
};

#pragma pack(2)
struct C
{
    char a;
    int b;
    short c;
};

#pragma pack(1)
struct D
{
    int a;
    char b;
    short c;
};

int _tmain(int argc, _TCHAR* argv[])
{

    cout << sizeof(A) << "   "<< sizeof B << "   "<< sizeof C << "   "<< sizeof D <<endl;
    return 0;
}

运行结果如下:

<span style="color:#3366ff">8     12     8       7</span>

理论上来说,结构体A与B的大小应该都是一样的,造成这种原因的就是字节对齐引起来的。  

  2.为什么要字节对齐  

  为什么呢?简单点说:为了提高存取效率。字节是内存空间分配的最小单位, 在程序中,我们定义的变量可以放在任何位置。其实不同架构 的CPU在访问特定类型变量时是有规律的,比如有的CPU访问int型变量时,会从偶数地址开始读取的,int类型占用4个字节(windows平台)。 0X0000,0X0004,0X0008.....这样只需要读一次就可以读出Int类型变量的值。相反地,则需要读取二次,再把高低字节相拼才能得到 int类型的值,这样子看的话,存取效率当然提高了。  通常写程序的时候,不需要考虑这些情况,编译都会为我们考虑这些情况,除非针对那些特别架构的 CPU编程的时候的则需要考虑 。当然用户也可以手工控制对齐方式。

  3.编译器对字节对齐的一些规则     

  我从下面三条说明了编译器对字节处理的一些原则。当然除了一些特殊的编译器在处理字节对齐的方式也不一样, 这些情况我未碰到过,就不作说明了。

  a. 关于数据类型自身的对齐值,不同类型会按不同的字节来对齐。

类型对齐值(字节)
char1
short2
int4
float4
double8

      b. 类、结构体的自身对齐字节值。对于结构体类型与类对象的对齐原则:使用成员当中最大的对齐字节来对齐。比如在Struct A中,int a的对齐字节为4,比char,short都大,所以A的对齐字节为4

     c. 指定对齐字节值。(意思是指在VC中使用指令: #pragma pack(n)来指定的对齐值,如果不指定,在VS2010默认为8)

     d. 类、结构及成员的有效对齐字节值。有效对齐值=min(类/结构体/成员的自身对齐字节值,指定对齐字节值)。   有效对齐值决定了数据的存放方 式,sizeof 运算符就是根据有效对齐值来计算成员大小的。简单来说, 有效对齐其实就是要求数据成员存放的地址值能被有效对齐值整除,即:地址值%有效对齐值=0 


    

          4. 结合编译器分析示例 


          根据上面的原则,分析Struct A的size。结构体的成员内存分配是按照定义的顺序来分析的。

struct A
{    
   int a;    
   char b;    
   short c;
}  

为了简单起见, 我假设Struct A存取的起始地址为 0x0000 在没有指定对齐值的情况下,分析步骤:

step 1: 根据第二条,首先为结构体选择对齐值:选择成员中最大的对齐值,即int a,对齐值为4
      
   step 2: 再根据第四条原则,决定有效对齐值:即然没有手工指定对齐值,则使用默认的值:4(windows 32平台)   

    step 3: int a 的有效地址值=min(4,4),(因为0x0000%4=0),这样a的地址就是从 0X0000~0x0003    

    step 4: char b 的有效对齐值=min(1,4),地址依次从0x0004 (因为Ox0004%1=0)开始,分配一个字节,地址段分配情况就是:0x0000~0x0004    

    step 5: short c 的有效对齐值=min(2,4),理论上说,分配的地址应该是连续的(从0x0005~0x00006),但是由于要求考虑到对齐的情况,所求要求地址段 偏移,这样就从0x0006(Offset+1,因为0x0006%2=0)开始,分配2个字节的地址0x0006~0x0007.

     
   目前为止,地址段的分配情况就是:0x0000~0x0007这样sizeof(A)的大小=0x0000~0x0007共8个字节大小,同时,8%4=0保证了Struct A的地址段与4成偶数倍。

接下来分析Struct B的大小,同样假设Struct B的起始地址为0x0000,分析步骤如下:

struct B
{
    char a;
    int b;
    short c;
}
step 1: 确实结构体B对齐值:选择成员中最大的对齐值,即int a,对齐值为4
      
   step 2: 确定手工指定对齐值,使用默认的值:4(windows 32, VC6.0平台)   

   step 3: char a 的有效地址值=min(1,4),a的地址就是 0X0000(因为0x0000%1=0)   

   step 4: int b 的有效对齐值=min(4,4),地址依次从0x0004~0x0007 (因为Ox0004%1=0)开始,分配4个字节,目前j地址段分配情况就是:0x0000~0x0007    

    step 5: short c 的有效对齐值=min(2,4),c从0x0008~0x0009(因为0x0008%2=0)开始,偏移2个字节的地址0x0006~0x0007.
    
    至止,地址段的分配情况就是:0x0000~0x0009共10个字节,但是Struct B的对齐值为4,这就要求地址地段再偏移2个字节,这样就是从0x0000~0x000B共12(因为12%4=0)个字节大小。这样,sizeof(B)=12
<span style="color:#333333">再来使用Pragma手工更改了字节对齐值的情况,先看看Struct C的定义:</span>

 
#pragma pack(2)
struct C
{
    char a;
    int b;
    short c;
};

 

  在代码中,手工指定了对齐值为2个字节,分析步骤如下:

 

step 1: 确定结构体C对齐值:选择成员中最大的对齐值,即int a,对齐值为4
      
   step 2: 确定手工指定对齐值,使用手工指定的值:2

   step 3: char a 的有效地址值=min(1,2),(因为0x0000%2=0),这样a的地址就是0x0000 

   step 4: int b 的有效对齐值=min(4,2),地址依次从0x0002~0x0005 (因为Ox0002%2=0)开始,分配4个字节,目前地址段分配情况就是:0x0000~0x0005    

    step 5: short c 的有效对齐值=min(2,2),由于要求考虑到对齐的情况,从0x0006(因为0x0006%2=0)开始,分配2个字节的地址0x0006~0x0007

     
   目前为止,地址段的分配情况就是:0x0000~0x0007共8个字节,同时也保证了Struct C的对齐情况(2字节对齐,pragma(2)),sizeof(C)=8

如果手工指定了对齐值为8个字节

#pragma pack(8)
struct C
{
    char a;
    int b;
    short c;
};

分析步骤如下:

step 1: 确定结构体C对齐值:选择成员中最大的对齐值,即int a,对齐值为4
      
   step 2: 确定手工指定对齐值,使用手工指定的值:8

   step 3: char a 的有效地址值=min(1,8)=1,(因为0x0000%1=0),这样a的地址就是0x0000 

   step 4: int b 的有效对齐值=min(4,8)=4,地址依次从0x0004~0x0007 (因为Ox0004%4=0)开始,分配4个字节。目前地址段分配情况就是:0x0000~0x0007。    

   step 5: short c 的有效对齐值=min(2,8)=2,从0x0008(因为0x0008%2=0)开始,分配2个字节的地址0x0008~0x0009。目前地址段分配情况就是:0x0000~0x0009。
 
   目前为止,地址段的分配情况就是:0x0000~0x0009共10个字节,但是Struct C的对齐值为4,这就要求地址地段再偏移2个字节,这样就是从0x0000~0x000B共12(因为12%4=0)个字节大小。这样,sizeof(C)=12

 

如果手工指定了对齐值为1个字节:

 

#pragma pack(1)
struct D
{
    int a;
    char b;
    short c;
};
这种情况非常简单,对齐值为1,因为1可以被任何数据整除,所以Struct D的成员变量存取顺序是连续的,这样就好办了,sizeof(D)=sizeof(int)+sizeof(char)+sizeof(short)=4+1+2=7 (比如从0x0000~0x0006)。这样,sizeof(D)=7。

说一下,

#pragma pack(n)

上式中:

n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。

考虑一种情况:

struct B
{
    char a;
    int b;
    A struct1;
    short c;
};

可做如下分析:

从前面的分析可知,sizeof(A)=8,因此成员中最大的对齐值为8,而不再是int的4。
step 1: 确定结构体B对齐值:选择成员中最大的对齐值,对齐值为8
      
   step 2: char a 的有效地址值=min(1,8)=1,(因为0x0000%1=0),这样a的地址就是0x0000 

   step 3: int b 的有效对齐值=min(4,8)=4,地址依次从0x0004~0x0007 (因为Ox0004%4=0)开始,分配4个字节。目前地址段分配情况就是:0x0000~0x0007。    

   step 4: struct1 的有效对齐值为min(8,8)=8,地址依次从0x0008~0x000F (因为Ox0008%8=0)开始,分配8个字节。
 
   step 5: short c 的有效对齐值=min(2,8)=2,从0x000F(因为0x0010%2=0,即16%2)开始,分配2个字节的地址0x0010~0x0011(即16~17)。目前地址段分配情况就是:0x0000~0x0011,总共18个字节大小。
   目前为止,地址段的分配情况就是:0x0000~0x0011 (0~17) 共18个字节大小,但是Struct C的对齐值为4(int),这就要求地址地段再偏移2个字节,这样就是从0x0000~0x0014共20(因为20%4=0)个字节大小。这样,sizeof(C)=20

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值