内存对齐与#pragma pack的理解

一、内存对齐的原因

  大部分的参考资料都是如是说的:

  1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

  2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

  二、对齐规则

  每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。

  规则:

  1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

  2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

  3、结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

  Win32平台下的微软C编译器(cl.exefor 80×86)的对齐策略:

  1) 结构体变量的首地址是其最长基本类型成员的整数倍;

  备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能是该基本数据类型的整倍的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。

  2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);

  备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。

  3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。

  备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。

  4) 结构体内类型相同的连续元素将在连续的空间内,和数组一样。

  5) 如果结构体内存在长度大于处理器位数的元素,那么就以处理器的倍数为对齐单位;否则,如果结构体内的元素的长度都小于处理器的倍数的时候,便以结构体里面最长的数据元素为对齐单位。

 

 

      最近调试网络的服务端程序,自己写了一个小客户端程序来测试,发现服务程序解包错误。经调试发现客户端的协议头大小和服务器端的协议头大小不一致。原因是服务器端加了#pragma pack(1),而客户端没加。

    之前没接触过这个编译宏,现在来认真学习之。

    首先google~~

    原来#pragma pack有几种形式,我所接触到的是#pragma pack(n),即变量以n节对齐。

    变量对齐在每个系统中是不一样的,默认的对齐方式能有效的提高cpu取指取数的速度,但是可能会浪费一定的空间。在网络程序中采用#pragma pack(1),即变量紧缩,不但可以减少网络流量,还可以兼容各种系统,不会因为系统对齐方式不同而导致解包错误。

    了解了概念和优点,现在我们就来测试之~

 

    平台:CPU—Pentium E5700 内存—2G

     1.操作系统:ubuntu 11.04 32bit   编译器:G++ 4.5.2

     2.操作系统:windows xp              编译器:VS2010

 

    先看第一个测试。

    结构体在正常情况和紧缩情况在以上不同环境下占用的内存大小。

1 struct pack {
2   int i;
3   short s;
4   double d;
5   char c;
6   short f;
7 }

  
   测试结果为:

    1
   

 

    2
    
  
    测试结果分析:

 

 

    可以看出紧缩后结构体的大小为15,是结构体内置类型大小的和。但是在默认情况下,结构体的大小都是对齐字节数的倍数。ubuntu下pack只需要20个字节,而windows要24个字节。这是因为ubuntu是以4字节对齐,而windows则是以最大的内置类型的字节数对齐,在结构体内最大的内置类型为double,其大小为8个字节。他们在内存中的对齐方式如下图:

    1

    

    2

    

   还需注意的是,在对齐类型的内部都是以2字节对齐的。

   结论:在默认情况下,linux操作系统是以4字节对齐windows操作系统则是以最大的内置类型对齐。

 

   第二个测试

   一个结构体内包含另外一个结构体,其大小的情况。

   内部的结构体为

1 struct pack {
2   short s;
3   double d;
4 }

   外部的结构体为

   1 struct complex _pack{

2     char  c;
3     struct  pack s;
4     double  d;
5  };

    我们有四种情况:

     1.      pack紧缩,complex _pack紧缩

     2.      pack紧缩,complex _pack默认

     3.      pack默认,complex _pack紧缩

     4.      pack默认,complex _pack默认

    以下的排列均按此顺序。

     测试的结果

      1

      

     2

      

    测试结果分析:

    在两个操作系统下,除了第一种情况----内结构体和外结构体都紧缩----相同之外,其他三种情况都不相同。我们可以根据偏移画出结构体在内存中的情况。第一种情况省略。

     1

     

     2

     

    结论:#pragma pack只影响当前结构体的变量的对齐情况,并不会影响结构体内部的结构体变量的排列情况。或者说#pragma pack的作用域只是一层。我们由第三种情况,内部结构体正常,外部结构体紧缩,可以得出结构体的对齐是按偏移计算的。

    这里还有一个问题没解决,为什么第二种情况内部结构体的偏移都是1?不是4或者8?

转自: http://cache.baidu.com/c?m=9d78d513d9d437a94f9993697c17c0161f4381132ba7d00208df8438e7732b47506793ff66351174d3c77e6d16ae394b9af72103311421c78cb98e5daeca8559259f5640671df65662d40edecd5125b037e02cfed81af0cafb25e3a5c5d2af4322bb44727d97f1fb4d7010dd19f60340e7b1ee38025e60ad9c44728f5e6058ec3434c053bea06c615dc7b09f4b4db037cd2454d2b835c76f50f743fa424e3344d71dec574c6663bc1a70e2177d55c8b019b037794220b555b1bcdcb5ec558fdbe93393fcdcaf58cf26b99b&p=8b2a9059a4b106fa10bd9b7d0964&user=baidu&fm=sc&query=%D4%F5%C3%B4%B2%E9%BF%B4VS2008%B5%C4%C4%AC%C8%CF%B5%C4+%BD%E1%B9%B9%CC%E5%B6%D4%C6%EB%CF%B5%CA%FD&qid=c02bb59f17cfeb3a&p1=9
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值