关于C/C++的字节对齐

转自http://goo.gl/H5EKg  作者靖难

最近研究C++的数据类型和数据大小时发现,字节对齐实际上是一个C/C++程序员无法忽视的一个问题.所以进行了初步的研究.

1.为什么会出现内存对齐问题-从x86 CPU架构说起
相信绝大多数计算机系和软件学院的学生都学过计算机组成原理这门基础课程,所以不作入门引导了,没有相关背景的可以补补相关课程资料.常有人问我,既然是按字节寻址方式,为何会出现字节对齐问题呢?难道是内存不是连续的吗?
这个问题实际上和CPU与内存的连接方式有关,我们知道CPU会通过地址总线Address BUS与CPU连接用于寻址,用Data Bus数据总线连接用于获取数据,而内存通常是RAM构成的复杂阵列下面我们看看32位CPU的连接图吧

对于这个RAM阵列(实际内存可能更复杂,但是原理类似), 前16个内存地址排列对应内存单元关系如下:

12 13 14 15
8 9 10 11
4 5 6 7
0 1 2 3


对照上图,是不是发现,对于每个内存操作周期,每个地址实际上可以读取4个bytes的数据呢,那么假设你有一个int型的数据存储在地址1或者2或者3,那么,是不是一个CPU内存时钟周期不够取出这个数据呢?这样是不是效率会很低下?这也就是为什么我们需要内存对齐了,虽然现代CPU和编译器已经对此做了很多优化,但是C/C++由于特殊性,程序员必须了解这个细节.

2.C/C++内存对齐的方式
C/C++中的结构/类的成员变量在对齐在内存中的排列是与它们摆放的先后顺序相关的,先看看下面的结构

1  struct malign_a
2 {
3     char a;
4     short b;
5     char c;
6     int d;
7 };

这个结构的sizeof(malign_a)是多少呢?1+2+1+4 =8?吗?这个实际上不对,那么,如何对齐的呢?
由于char是1个byte,所以不管他在什么位置,都可以用一个内存周期读出数据,short是半个字也就是2 bytes,这个时候假如它的地址是某个边界位置上,那么,也需要两个内存周期来读取,以此类推.所以,内存对齐就是加入填充padding无意义的数据来保证某个数据位于一个可以通过最少内存周期的位置,比如double型只有地址位于%4等于0的位置,才能保证2个周期读出.

同时对于每个具体平台,不同的编译器有不同的指定的对齐模式,比如C/C++可以通过

1  #pragma pack(n)  //n为1,2,4,8,16等

来指定对齐.当然,一般x86 32位机器下,都是默认4字节对齐的.这个n也称为对齐模数.
对齐策略如下:

1.结构体成员对齐:按某成员数据本身大小以及指定大小中较小者对齐
2.结构体整体对齐:按所有成员中最大者和对齐模数中较小者对齐

所以上述结构体按默认对齐方式如下:

1.a是char类型,可以在任意位置,位于位置0
2.b是short类型,2比默认的4小,所以按2字节对齐,所以在a后面填充1个字节,b必须位于位置2,占两个字节
3.c是char类型,位于位置4
4.d是int类型,4<=4,所以按4字节对齐,需要在c后面填充3个字节,位置为8
5.由于最大成员是4,默认是4,所以结构已经按4对齐,所以总字节数为12

d
c padding
a padding b

再把原来的结构体成员位置改动一下:

1  struct malign_b
2 {
3     char a;
4     char c;
5     short b;
6     int d;
7 };

这个结构的大小为8,对齐过程如下:

1.a是char类型,可以在任意位置,位于位置0
2.c是char类型,位于位置1
3.b是short类型,2比默认的4小,所以按2字节对齐,这时候恰好位于位置2
4.d是int类型,4 5.由于最大成员是4,默认是4,所以结构已经按4对齐,所以总字节数为8

d
a c b

最后为了加深理解,我们来看看下面这个结构按对齐模数2对齐时候的memory layout

01  #pragma pack(push)
02  #pragma pack(2)
03  struct malign_b
04 {
05     char a;
06     int d;
07     char c;
08     short b;
09 };
10  #pragma pack(pop)

它的大小为10,当指定为对齐模数2时候

1.由于a是char,1<2,位于位置0
2.由于d是int,4>2,所以按2字节对齐,所以必须在a后面填充一个字节,位于位置2,
3.c是char,位置为6
4.b是short,所以必须在c后面填充一个字节,位置为8
5.最大的是4,所以按2对齐,上述结果其实已经整体是按2对齐,所以总数为10

b

   

byte 2 and 3 of d

c

padding

a

padding

byte 0 and 1 of d

我相信通过以上例子,应该可以熟悉字节对齐过程了,如果有不妥之处,敬请留言指出,谢谢.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的字节对齐(内存对齐)是指在分配内存时,将变量或结构体的起始地址对齐到特定的字节边界。这样做有助于提高内存访问的效率和性能。字节对齐的规则可以通过编译器选项或特定的关键字进行控制。 以下是关于C++字节对齐的一些重要概念和规则: 1. 默认对齐: - 编译器会使用默认的对齐规则来分配内存。通常,默认对齐值是被编译器设置的,一般为结构体成员中最大的对齐值。 2. 对齐值: - 对齐值是指要求变量或结构体的起始地址必须是该值的倍数。常见的对齐值有1、2、4、8等。 3. 对齐修饰符: - C++11引入了对齐修饰符 `alignas`,允许开发者显式地指定变量或结构体的对齐值。 4. 结构体字节对齐: - 结构体的字节对齐规则是,结构体的起始地址必须是其成员中最大对齐值的倍数。 - 编译器会在结构体成员之间插入填充字节,以保证对齐要求。 5. 类对象字节对齐: - 类对象的字节对齐规则与结构体类似,但还受到继承关系的影响。 - 派生类的起始地址必须满足其成员的对齐要求,并且满足其基类中最大对齐值的倍数。 为了控制字节对齐,可以使用编译器提供的特定选项(如`#pragma pack`)或关键字(如`alignas`)。具体的字节对齐规则和选项可能因编译器和平台而异,因此在编写代码时最好参考特定编译器的文档。正确的字节对齐可以提高内存访问性能,并确保与其他代码或外部系统的兼容性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值