字节对齐原理1
1. 字节对齐:
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量(内置类型)的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
2. 字节对齐的原因:
各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。
默认CPU总线读取4字节,然后根据类型剔除,变量存放的地址能够整除变量类型,
struct stu
{
int a;
char b;
}st[10];//结构体的大小为8,要在后面添加3个字节,为了数组下一个元素对齐和对一个元素顺序存放
因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充3个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构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] */ //这样可以很清楚的看到非对齐访问是如何产生错误的 //以及如何消除非对齐访问带来问题 //也可以看到非对齐访问和对齐访问的指令差异导致效率问题 }