字节对齐原理

  1.  字节对齐:

      现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量(内置类型)的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

2.  字节对齐的原因:

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

默认CPU总线读取4字节,然后根据类型剔除,变量存放的地址能够整除变量类型,

、,因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.

3. 设置对齐方式

 #pragma pack(n),设置按照n字节对齐,每个类型成员的存放地址可以整除n;#pragma pack(),取消对齐方式,默认形式为4字节对齐;

4.编译器的对齐原则

    a.  对于内置类型来说,按照自身的类型大小对齐,char存放的地址可以整除1,int,float 可以整除4,double可以整除8,short可以整除2;

   b.   结构体和类的自定义类型,对齐方式按照数据成员最大类型方式存取。

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

   d.  最终存放所需的字节 数要能乘除最大类型

  e. 结构体数组也是字节对齐

5.修改编译器的字节对齐;

.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项

的Struct Member Alignment中修改,默认是8字节。 2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma而不是progma.

6.  ARM编译器对齐方式

    对齐的使用: 1.__align(num)     这个用于修改最高级别对象的字节边界。在汇编中使用LDRD或者STRD时     就要用到此命令__align(8)进行修饰限制。来保证数据对象是相应对齐。     这个修饰对象的命令最大是8个字节限制,可以让2字节的对象进行4字节     对齐,但是不能让4字节的对象2字节对齐。     __align是存储类修改,他只修饰最高级类型对象不能用于结构或者函数对象。    2.__packed    __packed是进行一字节对齐    1.不能对packed的对象进行对齐    2.所有对象的读写访问都进行非对齐访问    3.float及包含float的结构联合及未用__packed的对象将不能字节对齐    4.__packed对局部整形变量无影响    5.强制由unpacked对象向packed对象转化是未定义,整形指针可以合法定    义为packed。       __packed int* p;   //__packed int 则没有意义    6.对齐或非对齐读写访问带来问题    __packed struct STRUCT_TEST {    char a;    int b;    char c; }   ;     //定义如下结构此时b的起始地址一定是不对齐的           //在栈中访问b可能有问题,因为栈上数据肯定是对齐访问[from CL] //将下面变量定义成全局静态不在栈上 static char* p; static struct STRUCT_TEST a; void Main() { __packed int* q;   //此时定义成__packed来修饰当前q指向为非对齐的数据地址下面的访问则可以 p = (char*)&a;          q = (int*)(p+1);     

*q = 0x87654321; /*   得到赋值的汇编指令很清楚 ldr       r5,0x20001590 ; = #0x12345678 [0xe1a00005]    mov       r0,r5 [0xeb0000b0]    bl        __rt_uwrite4   //在此处调用一个写4byte的操作函数       [0xe5c10000]    strb      r0,[r1,#0]    //函数进行4次strb操作然后返回保证了数据正确的访问 [0xe1a02420]    mov       r2,r0,lsr #8 [0xe5c12001]    strb      r2,[r1,#1] [0xe1a02820]    mov       r2,r0,lsr #16 [0xe5c12002]    strb      r2,[r1,#2] [0xe1a02c20]    mov       r2,r0,lsr #24 [0xe5c12003]    strb      r2,[r1,#3] [0xe1a0f00e]    mov       pc,r14 */ /* 如果q没有加__packed修饰则汇编出来指令是这样直接会导致奇地址处访问失败 [0xe59f2018]    ldr       r2,0x20001594 ; = #0x87654321 [0xe5812000]    str       r2,[r1,#0] */ //这样可以很清楚的看到非对齐访问是如何产生错误的 //以及如何消除非对齐访问带来问题 //也可以看到非对齐访问和对齐访问的指令差异导致效率问题 }

posted @ 2014-10-19 15:50  李艳21  阅读(383)  评论(0编辑  收藏  举报