详解4字节对齐

-----------------其实我也是一条分割线------------------

 

所谓的字节对齐,就是各种类型的数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这个就是对齐。我们经常听说的对齐在N上,它的含义就是数据的存放起始地址%N==0。具体对齐规则会在下面的篇幅中介绍。首先还是让我们来看一下,为什么要进行字节对齐吧。

各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU,诸如SPARC在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构上必须编程必须保证字节对齐。而有些平台对于没有进行对齐的数据进行存取时会产生效率的下降。让我们来以x86为例看一下如果在不进行对齐的情况下,会带来什么样子的效率低下问题,看下面的数据结构声明:

 

 

  1. struct A {   
  2.     char c;   
  3.     int i;   
  4. };   
  5. struct A a;  

 

Code 13-1

假设变量a存放在内存中的起始地址为0x00,那么其成员变量c的起始地址为0x00,成员变量i的起始地址为0x01,变量a一共占用了5个字节。当CPU要对成员变量c进行访问时,只需要一个读周期即可。而如若要对成员变量i进行访问,那么情况就变得有点复杂了,首先CPU用了一个读周期,从0x00处读取了4个字节(注意由于是32位架构),然后将0x01-0x033个字节暂存,接着又花费了一个读周期读取了从0x04-0x074字节数据,将0x04这个字节与刚刚暂存的3个字节进行拼接从而读取到成员变量i的值。为了读取这个成员变量iCPU花费了整整2个读周期。试想一下,如果数据成员i的起始地址被放在了0x04处,那么读取其所花费的周期就变成了1,显然引入字节对齐可以避免读取效率的下降,但这同时也浪费了3个字节的空间(0x01-0x03)

有了上述的基本概念之后,让我们来看一下,编译器是按照什么样的原则进行对齐的。首先有3个重要的概念:自身对齐值,指定对齐值和有效对齐值。

自身对齐值:即数据类型的自身的对齐值。例如char型的数据,其自身对齐值为1字节;short型的数据,其自身对齐值为2字节;int,float,long类型,其自身对齐值为4字节;double类型,其自身对齐值为4字节;structclass类型的数据其自身对齐值为其成员变量中自身对齐值最大的那个值。

指定对齐值:#pragma pack (value)时指定的对齐值value

有效对齐值:上述两个对齐值中最小的那个。

我们一般说的对齐在N上,都是指有效对齐在N上。说了这么多,还是让我们先来看一些例子来加深对这些概念的理解吧。例:假设在x86机器上,假设编译器按默认4字节进行对齐

 

  1. struct A {   
  2.     char c;   
  3.     int i;   
  4.     short s;   
  5. };   
  6.   
  7. #pragma pack (2)   /* 指定按2字节对齐 */   
  8. struct B {   
  9.     char c;   
  10.     short s;   
  11.     int i;   
  12. };   
  13. #pragma pack ()    /* 恢复默认对齐 */  

 

Code 13-2

让我们来考虑一下sizeof(struct A)sizeof(struct B)的结果各应该是什么。首先来看sizeof(struct A),假设A的起始地址为0x00,做这样的假设只是为了更方便理解,其实A始终被放在对齐边界上,这并不影响sizeof的结果,在接下来的例子中,我们也会继续沿用这个假设。言归正传,数据成员c的自身对齐值=1,指定对齐值=4(默认),所以其有效对齐值为1,因0x00%1==0,所以它被存放在0x00处;数据成员i的自身对齐值=4,指定对齐值=4,可得出其有效对齐值为4,因0x01%4 != 0,因此它应该被存放在0x04地址处,占用0x05,0x06,0x074个字节;接下来看数据成员s的自身对齐值=2,指定对齐值=4,得出有效对齐值为2,因0x08%2 == 0,因此它被存放在起始地址为0x08处,并占用2字节;最后再看数据结构A自身的对齐值=4(最大数据成员自身对齐值),指定对齐值=4,得有效对齐值为4,因0x0A%4 != 0,因此多占用0x0A0x0B为结构体A所用(这一步的作用是基于结构体数组的出发,对于结构体或者类,要将它们补充成其有效对齐值的整数倍,这点请千万注意)。由此可见sizeof(struct A)的结果应该是=1+3(空闲空间)+4+2+2(结构体补充)=12字节。

接下来让我们考察sizeof(struct B)的结果。这里需要注意的是,在B被声明前,指定对齐值已经被设置为2个字节。数据成员c的有效对齐值为1,存放起址0x00s的有效对齐值为2,存放起址0x02i的有效对齐值也为2,存放起址为0x04,累加起来一共是8个字节,已经是数据结构B的有效对齐值2的整数倍了。因此sizeof(struct B)的结果8个字节。

看到这里,应该对字节对齐有了一定的了解了吧。接下来我们要看一个更加复杂的例子:假设在x86机器上,假设编译器按默认4字节进行对齐

 

  1. #pragma pack(8)   
  2. struct S1 {   
  3.     char a;   
  4.     long b;   
  5. };   
  6.   
  7. struct S2 {   
  8.     char c;   
  9.     struct S1 d;   
  10.     long long e;   
  11. };   
  12. #pragma pack()   
  13.    

 

Code 13-3

运用上面所学到的知识,应该不难得出sizeof(struct S1)的值为8字节,其中a的有效对齐值为1b的有效对齐值为4,结构S1的有效对齐值为4。现在让我们来看看sizeof(struct S2)的值会是多少呢?首先成员c的有效对齐值为1S1的自身对齐值为成员的最大自身对齐值,即4字节,其指定对齐值为8,则其有效对齐值也为4,存放起址应该为0x04,并且占用8个字节(0x04+0x08=0x0C),其中0x01-0x03被用来填充。接下去数据成员e的有效对齐值为4,存放起址应该是0x0D % 8 == 0,占用8个字节(0x10)。最后考察S2本身的有效对齐值应该是4字节,而0x0D%8==0,就不需要填充了,因此sizeof(struct S2)=20

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在C++中,结构体和类都是由多个成员变量组成的。为了在内存中高效地存储这些成员变量编译器会对结构体和类进行内存字节对齐。 内存字节对齐是指将结构体或类中的成员变量按照一定的规则排列,使得每个成员变量的内存地址都是其长度的整数倍。这样一来,访问这些成员变量时就可以减少内存访问次数,提高访问效率。 内存字节对齐的规则如下: 1. 结构体或类的起始地址必须是其最宽基本类型成员的整数倍。 2. 结构体或类的每个成员变量相对于起始地址的偏移量必须是其类型大小的整数倍。 3. 结构体或类的总大小必须是其最宽基本类型成员大小的整数倍。 例如,一个结构体中有两个成员变量,一个是int类型,一个是char类型。如果按照默认的字节对齐规则排列,结构体的内存布局如下: ``` struct MyStruct { int a; char b; }; // 内存布局 // +---+---+ // | a | b | // +---+---+ ``` 这里,int类型占用4个字节,char类型占用1个字节。因此,编译器会按照4字节对齐的方式排列结构体。由于int类型是最宽的基本类型,所以结构体的起始地址必须是4的倍数,而char类型则放在了4字节边界上。 需要注意的是,内存字节对齐规则可能会因为编译器的不同而产生变化。有些编译器允许开发者通过预处理指令来指定结构体的对齐方式,例如: ``` struct MyStruct { int a; char b; } __attribute__((aligned(8)))); ``` 这里,`__attribute__((aligned(8)))`表示MyStruct结构体需要以8字节对齐的方式排列。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值