C/C++数据对齐

Data Alignment

 关于数据对齐问题,现在多多少少有了一些接触,简单地说下自己的看法。


1、对齐的背景

 大端和小端的问题有必要在这里介绍一下,计算机里面每个地址单元对应着一个字节,一个字节为8bit,对于位数大于8位的处理器来说,寄存器的宽度是大于一个字节的,例如16bit的short型变量x,在内存中的地址是0x0010,x的值为0x1122,0x11为高字节,0x22为低字节,常用的X86结构是小端模式,很多ARM,DSP都是小端模式,而KEIL C51则为大端模式。内存空间是按照byte进行划分的,理论上对任何类型的变量的访问可以从任何地址开始,但实际上访问特定变量的时候经常在特定的内存地址访问,这就需要各类型的数据按照一定的规则在空间上排列,而不是顺序排列,这就是对齐。


2、对齐的原因

 不同硬件平台对存储空间的处理是有很大不同的,一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况, 但是最常见的是如果不按照适合其平台的要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为 32位)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低 字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。


3、对齐的实现

 通常我们在写代码的时候是不需要考虑对齐的影响的,都是依赖编译器来为我们选择适合的对齐策略,我们也可以通过传递给编译器预编译指令来指定数据对齐的方法。


 以struct数据结构的sizeof方法为例,环境是Mac OS X 64位内核,结构体的定义如下:

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

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

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

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

int main(int argc, char** argv)
{
 printf("size of struct A : %lu \n", sizeof(struct A));
 printf("size of struct B : %lu \n", sizeof(struct B));
 printf("size of struct C : %lu \n", sizeof(struct C));
 printf("size of struct D : %lu \n", sizeof(struct D));
 return 0;
}

输出:
size of struct A : 8
size of struct B : 12
size of struct C : 8
size of struct D : 7

 结构体中包含了4字节长度的int一个,1字节长度的char一个以及2字节长度的short一个。加起来所用到的内存空间为7个字节,但实际使用sizeof时发现,结构体之间占用的内存是不一样的。

 关于对齐有几个需要说明的:
 (1)数据类型自身的对齐值:基本数据类型的自身对齐值,char类型为1,short类型为2,int,float,double为4;
 (2)指定对齐值:#pragma pack(value)时的指定对齐值value;
 (3)结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值;
 (4)数据成员、结构体和类的有效对齐值:自身对齐值或指定对齐值中较小值。
 对于一个具体的数据结构的成员和其自身的对齐方式,有效对齐值N将最终决定数据存放地址的方式的值,对齐在N上就意味着数据“存放的起始地址%N=0”,

 下面来针对上面的例子进行分析:
 struct B {
  char b;
  int a;
  short c;
 };
 假设B从地址空间0x0000开始,默认的对齐值是4(这里有个问题想请教大家,我的是64位的内核,但是测试我的默认对齐方式为4),第一个成员变量b的自身对齐值为1,比默认值小所以有效对齐值为1,存放地址0x0000%1=0,第二个成员变量a,自身对齐值为4,存放的起始地址为0x0004到0x0007这个4个连续的字节空间中,0x0004%4=0,第三个变量c,自身对齐值为2,存放的起始地址为0x0008到0x0009,地址同样符合要求。结构体B的自身对齐值为变量中的最大对齐值(b)4,(10+2)%4=0,所以0x000A到0x000B也是被结构体B占用。
 内存中的示意图:
 b - - -
 a a a a
 c c

 #pragma pack(2)
 struct C {
  char b;
  int a;
  short c;
 };
 #pragma pack()
 第一个变量b的自身对齐值为1,指定对齐值为2,有效对齐值为1,b存放在0x0000,a的自身对齐值为4,大于指定对齐值,所有有效的对齐值为2,a占有的字节为0x0002、0x0003、0x0004、0x0005四个连续字节中,c的自身对齐值为2,所以有效对齐值也是2,顺序存放在0x0006、0x0007。结构体C的自身对齐值为4,所以有效对齐值为2,8%2=0。
 内存中的示意图:
 b -
 a a
 a a
 c c

 其实想到内存中的示意图一切都会简单很多。

你可能感兴趣的:(c,编译器,alignment,数据对齐,处理器)