数据结构对齐
数据结构对齐
数据结构对齐是数据在计算机内存中存储和访问的方式。它包括两个独立但相关的问题:数据对齐和数据结构填充。现代计算机每次以一个存储器字的大小(例如4字节在一个32位的操作系统上)访问内存。数据对齐意味着把数据存储到能被存储器字大小整除的地址上,以便提高系统的性能。数据结构填充是指为了对齐数据,在上一个数据的结束和下一个数据的开始之间填充一些无意义的字节。例如一个32位的操作系统在读取一个奇数地址的数据时,它要么访问两次内存并做一些处理才能得到所需的数据,要么返回一个地址对齐错误。即使上一个数据结束于奇数地址,也需要填充一些字符使得下一个数据开始4字节(以32位操作系统为例)对齐地址。
数据对齐:
非对齐访问会带来一些问题。
1:当数据的最高位和最低位不在一个存储器字上时,计算机必须要分割该数据并多次访问内存。此时计算机必须能够在访问不同的内存单元时还能够正确的处理TLB miss或者page fault等错误。
2:对齐访问是原子操作,但是非对齐操作不一定是原子操作。有可能设备A在非对齐访问时,设备B却已经修改了该非对齐数据的相关的存储器字。因此,非对齐数据不能用于互锁操作。
各体系结构对非对齐访问的处理:
1:RISC体系结构 大部分RISC处理器通过使用其他的指令(单字节load/store指令)来处理对齐错误
2:MIPS体系结构 MIPS处理器有专门的非对齐load、store指令
编译器对数据对齐的处理:
32位x86机器上,不同的操作系统不同的编译器运行出来的结果各不相同。仅以VC6和VS2008为例。
#include <stdio.h> #include <stdlib.h> #include <conio.h> struct testA { char a; char b; char c; char d; char e; }; char aa; char bb; char cc; struct testA dd; void test() { char a; char b; char c; struct testA d; printf("%p %p %p %p\n",&a, &b, &c, &d); printf("%p %p %p %p",&aa, &bb, &cc, &dd); getch(); } void main() { test(); }
32位x86 Windows xp系统下:
VC6下运行结果:
VS2008下运行结果:
本人暂时没找到相关方面的权威资料,还望高手解释~~!~~
数据结构填充:
为了使结构体(联合体和类)中每个成员相对于结构体的起始地址都是对齐的,我们可能在结构体中填充一些字节;为了使结构体数组中的每个结构体成员现对于结构体数组的起始地址都是对齐的,我们会在结构体的结束填充一些字节。
基础定义:
1:数据类型自身的对齐值
对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
2:结构体或者类的自身对齐值
其成员中自身对齐值最大的那个值。
3:指定对齐值
#pragma pack (n)等指令指定的对齐值n(n等于1或者2的正整数次幂)。
4:数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
5:n字节对齐:n等于1或者为2的整数次幂,并且数据的内存地址可以被n整除时。
当数据的大小不是2的正整数次幂时(譬如成员为3个char类型变量的结构体),该数据是否对齐的条件由所在的环境决定。
填充的规则:
1:结构体每个成员相对于结构体首地址的偏移量(offset)都是成员有效对齐值的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
2:结构体的总大小为结构体有效对齐值的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。
如下程序所示:
#include <stdio.h> #include <stdlib.h> #include <conio.h> struct testA { char a; char b; }; struct testB { char a; /*a后面填充了一个字节*/ short b; }; struct testC { char a; /*a后面填充了三个字节*/ int b; }; struct testD { char a; /*a后面填充了7个字节*/ double b; }; struct testE { char a; /*a后面填充了七个字节*/ double b; int c; /*d后面填充了四个字节*/ }; void test() { struct testA a; struct testB b; struct testC c; struct testD d; struct testE e; printf("%d %d %d %d %d\n",sizeof(a), sizeof(b), sizeof(c), sizeof(d), sizeof(e)); getch(); } void main() { test(); }
32位 x86 Windows xp系统上:
VC6和VS2008运行结果相同:
使用指令改变编译器默认对齐
尽管C和C++编译器不允许重新排列结构体成员以节省空间,但是其他语言可能允许。另外,我们可以通过一些伪指令指定对齐值,譬如#pragma pack(n)伪指令。
如下程序所示:
#include <stdio.h> #include <stdlib.h> #include <conio.h> #pragma pack(1) struct testA { char a; double b; }; #pragma pack() #pragma pack(8) struct testB { char a; short b; }; #pragma pack() struct testC { struct testA a; short c; }; void test() { struct testA a; struct testB b; struct testC c; printf("%d %d %d\n",sizeof(a), sizeof(b), sizeof(c)); getch(); } void main() { test(); }
32位x86 Windows xp系统下:
VC6和VS2008运行结果相同:
参考资料:
1:http://en.wikipedia.org/wiki/Data_structure_alignment
2:http://www.songho.ca/misc/alignment/dataalign.html
3:http://blog.csdn.net/achellies/article/details/4217165