数据对齐

来源:互联网 发布:模拟退火算法模型 编辑:程序博客网 时间:2024/04/27 11:28

为什么要数据对齐?

所谓数据对齐是指访问数据的地址要满足一定的条件,能被这个数据的长度所整除。 例如,1字节数据已经是对齐的,2字节的数据的地址要被2整除,4字节的数据地址要 被4整除。

但为什么要数据对齐呢?简单地说,数据对齐是为了读取数据的效率。假如说每一次 读取数据时都是一个字节一个字节读取,那就不需要对齐了,这跟读一个字节没有什 么区别,就是多读几次。但是这样读取数据效率不高。为了提高读取数据的带宽,现 代存储系统都采用许多并行的存储芯片来提高读取效率。以自然字长为4个字节的机器 为例。

memory chip     0       1       2       3offset    0             0       1       2       3  1             4       5       6       7  2             8       9      10      11  N            4N    4N+1    4N+2    4N+3

如上图所示,地址0-3的4字节数据都存储在4个不同的芯片上。CPU的bits 0-7连上芯 片0,bits 8-15连上芯片1,bits 16-23连上芯片2,bits 24-31连上芯片4。

如果从地址0读取4字节数据,4个存储芯片都从各自的地址0读出数据,而且位置也是 正确的。这4个字节的读取可以并行地进行,因此带宽是一次读取一个字节的4倍。

总结一下为什么需要数据对齐。为了增加CPU读取数据的带宽,内存系统通常都采用并 行结构使得可以并行传输数据。这样的并行结构使得访问对齐的数据速度快,但是若 要使访问不对奇的数据也一样快会使CPU与内存系统的接口变得更复杂,而这是划不来 的。经过权衡之后,最终的结果是:访问对齐的数据速度快,访问不对奇的数据速度 慢(需要2次访问)或干脆禁止访问不对奇数据。

关于访问对齐数据和不对齐数据的速度差异请 看这里。关于为什么使访问不对齐数据需要多次访问请 看这 里。关于哪些CPU禁止访问不对奇数据以及在这些CPU上如何解决访问不对奇 数据的问题请 看这里。

Visual C++的结构体对齐规则

每一个数据对象都有一个对齐限制,通常称为对齐模数。对于基本 类型(不包括结构体,联合体和数组)不同的数据类型的对齐模数通常等于这个数据 类型的大小,比如char的对齐模数为1,short的对齐模 数为2,int的对齐模数为4,float的对齐模数是 4,double的对齐模数为8。当然,这个对齐模数每一个编译器可能有差 异,并且可以设置。在Visual C++中可以通过/Zp选项 获#pragma设置。综合起来,基本类型的对齐模数是这个类型的大小和 设置的对齐限制的较小者。对于结构体,联合体和数组,对齐模数是它的成员的对齐 模数的最大值。

现举几个例子说明一下。(下面例子都在Visual C++ 2008 Express Edition上测试 过。)

struct A {    char c;    int i;};

c的对齐模数是1,i的对齐模数是4,因此结构 体A的对齐模数为4。c本身占用1个字节,由 于i的对齐模数为4,因此c后面填充3个字节以 使i的起始地址对齐。这样加起来是8个字节,A已经对齐 了,后面不用再填充了,因此A占用8个字节。

另一个例子。

struct A {    char c;    int i;    char d;};
跟上面一样,c的对齐模数是1,i的对齐模数,d的对齐模数是1,因此结构体A的对齐 模数为4。c占用1字节,为使i对齐,c后面要填充3个字节,因此cd占用8个字节。d 占用1个字节,这样一共占用9个字节,但是A的对齐模数是4,因此d后面要填充4字节。 因此A占用12字节。

 

对于比特域来讲,上面的规则要稍稍修改一下。对于连续的声明类型大小相同的比特域来讲,如果它们申请的宽度加起来不超过它们声明的类型大小,那么它们可以压缩在一个分配单元中。对于声明类型大小不同的比特与来讲,它们分配在不同的分配单元中。

struct A {    char a : 3;    char b : 5;};

 

ab的类型长度都为1字节,因此可以考虑合并。它们加 起来为8比特,可以放在一个字节中,由于A的对齐模数为1,因此不需在后面再填充数 据,所以A的大小为1字节。

另一个例子.

struct A {    char a : 4;    char b : 5;};
ab的类型长度都为1字节,因此可以考虑合并,但跟上 例不同的是,它们无法放入一个字节中(加起来为9比特),因此它们要放在各自的分 配单元中,ab之间有4比特的空隙。A的 大小为2字节。

 

再考虑如下例子.

struct A {    char a : 3;    int b : 2;    char c : 2;};
ab的类型长度不相等(a的为1字 节,b的为4字节),因此不能合并,同样bc也不能合并。因此,a单 独占用1字节,后面填充3字节以使b对齐,b占用4字节,c占用1字节。由于A的对齐模 数是4字节,所以c后面要填充3字节,A的大小为12字节。

 

下面再考虑匿名比特域的例子。匿名比特域主要目的是在两个比特域之间加入一段空隙。

struct A {    char a : 2;    char   : 2;    char b : 2;};
匿名比特域对对齐规则没有影响,它唯一的不同是无法引用这个域。由于这3个域的类 型大小是一样的,且可以放在一个字节之中,因此可以合并,A的大小为1字节。

 

下面再考虑匿名比特域宽度为0的,它的作用是使它后面的比特域对齐到下个比特域的对齐模数上,也就是说,不允许它跟上一个比特域合并。

struct A {    char a : 2;    char   : 0;    char b : 2;};
尽管这三个比特域的类型一样,但是由于ab之间有一个宽度为0的匿名比特域,因此 它们不能合并。ab个占用1个字节,所以A的大小为2字节。

 

最后,对于具有普通成员和比特域的结构体,它们的对齐规则跟上面的一样。注意,普 通成员与比特域不能合并,只有连续的比特域之间可以考虑合并,每一个成员都要对 齐到自己的对齐模数上。

首先 几个概念:

1.基本数据类型的自身对齐值:char自身对齐值为1,short为2,int,float为4,double为8,单位字节。

2.结构体或者类的自身对齐值:其成员变量中自身对齐值最大的那个值。

3.指定对齐值:#pragma pack (n)时的指定的对齐值n。

那么数据成员、结构体以及类最终按照几字节对齐:自身对齐值和指定对齐值中小的那个值。

然后有个公式: 存放起始地址 % 各个成员的自身对齐值 = 0

现在我们来看这个类

C/C++ code
?
1
2
3
4
5
6
7
8
9
10
class A
{
public:
double d;
int a;
char c;
float b;
A();
~A();
};

这里没有指定对齐值(就是没有#pragma pack (n))
那么这个类的的最终的对齐值是 8,就是按照8字节对齐

假定 地址空间从0x0000开始排放,
那么double d的地址为  0x0000 (0x0000 % 8 = 0) 

然后往后偏8字节之后,能被下一个成员int a这4字节对齐值整除的地址为0x0008
那么int a的地址为  0x0008 (0x0008 % 4 = 0)

然后往后偏4字节之后,能被char c这1字节对齐值整除的地址为0x000C
那么char c的地址为  0x000C (0x000C % 1 = 0)

然后往后偏1字节之后,能被float b这4字节对齐值整除的地址为0x0010(此时char c后面空出来了3字节)
那么float b的地址为  0x0010 (0x0010 % 4 = 0)

部分内容参考:http://blog.csdn.net/tigerscorpio/article/details/5933807

0 0
原创粉丝点击