"); //-->
绝大部分64位的Unix,linux都是使用的LP64模型;32位Linux系统是ILP32模型;64位的Windows使用的是LLP64(long long and point 64)模型。
各个硬件平台对存储空间的处理上有很大的不同,一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐。比如sparc系统,如果取未对齐的数据会发生错误,举个例:
————————————————
char ch[8]; char *p = &ch[1]; int i = *(int *)p;
运行时会报segment error,而在x86上就不会出现错误,只是效率下降。
例子1:
char ch[8]; char *p = &ch[1]; int i = *(int *)p;
运行时会报segment error,而在x86上就不会出现错误,只是效率下降。
对于32位机来说,4字节对齐能够使cpu访问速度提高,比如说一个long类型的变量,如果跨越了4字节边界存储,那么cpu要读取两次,这样效率就低了。但是在32位机中使用1字节或者2字节对齐,反而会使变量访问速度降低。所以这要考虑处理器类型,另外还得考虑编译器的类型。在vc中默认是4字节对齐的,GNU gcc 也是默认4字节对齐。
在设计不同CPU下的通信协议时,或者编写硬件驱动程序时寄存器的结构这两个地方都需要按一字节对齐。即使看起来本来就自然对齐的也要使其对齐,以免不同的编译器生成的代码不一样.
例子1
/************************ > File Name: struct_test.c > Author:Marvin > Created Time: Thu 22 Mar 2018 07:19:46 PM CST **********************/ #include<stdio.h> int main() { struct test { char a; short b; int c; long d; }; struct test t = {'a',11,11,11}; printf("size of struct t = %u\n", sizeof(t)); return 0; }
在64位centos上编译编译后结构struct test的布局如下:
由于要保证结构体每个元素都要数据对齐,因此必须在a和b之间插入1字节的间隙使得后面的short元素2字节对齐int元素4字节对齐long元素8字节对齐,这样最终test结构大小为16字节。
运行程序结果为:
size of struct t = 16
例子2
现在考虑这样一个结构体:
struct test2 { int a; long b; char c; }; struct test2 t2 = {11,11,'c'};
在64位centos上编译编译后结构struct test2的布局如下:
size of struct test2 = 24
例子3
不妨将结构体struct test2里面成员的顺序重新排列一下:
struct test3 { char c; int a; long b; }; struct test3 t3 = {'c',11,11};
在64位centos上编译编译后结构struct test2的布局如下:
运行结果为:
size of struct test3 = 16
见适当地编排结构体成员地顺序,可以在保存相同信息地情况下尽可能节约内存空间。
例子4
struct B { char b; int a; short c; };
假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求,0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了,因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
————————————————
例子5:
#pragma pack (2) /*指定按2字节对齐*/ struct C { char b; int a; short c; }; #pragma pack () /*取消指定对齐,恢复缺省对齐*/
对于结构体嵌套地情况,结构体对齐算法思想:深度优先填充。
padLen = getPadLen(offset , defaultLen); int getPadLen(int offsetLen, int defaultLen) { int vaildLen = min(packLen,defaultLen); if(0 == vaildLen || 0 == offsetLen % vaildLen) { return 0; } return vaildLen - (offsetLen % vaildLen); }
struct test1 { int a; long b; }; struct test4 { char a; struct test1 b; int c; }; struct test4 t4 = {'a', {11,11},11}
test1的内存分布:
test4的内存分布:
代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
unsigned int i = 0x12345678; unsigned char *p=NULL; unsigned short *p1=NULL; p=&i; *p=0x00; p1=(unsigned short *)(p+1); *p1=0x0000;
最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.
如果出现对齐或者赋值问题首先查看
编译器的big little端设置
看这种体系本身是否支持非对齐访问
如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作
举例:
#include<iostream> using namespace std; //windows 64 位默认 结构体对齐系数为8,32位 结构体对齐系数为4 //测试系统对齐系数 // #pragma pack(8) my_struct_1 为16字节 // #pragma pack(4) my_struct_1 为12字节 // 不加#pragma pack(8) my_struct_1 为16字节 //顾系统默认对齐系数为8 struct my_struct_1 { char a; //1 double b; //之前补7 +8 8/8==1 }; #pragma pack(4) struct my_struct_2 { char a; //1 double b; //3+8 int c; //4 16/4=4 }; #pragma pack() #pragma pack(2) struct my_struct_3 { char a; //1 double b; //1+8 int c; //4 14/2 }; #pragma pack() #pragma pack(4) struct my_struct_4 { char a[5]; //5 double b; //3+8 16/4 }; #pragma pack() #pragma pack(2) struct my_struct_5 { char a[5]; //5 double b; //1+8 14/2 }; #pragma pack() #pragma pack(4) struct my_struct_6 { char a; //1 char b[3]; //3 char c; //1 1+3+1 }; #pragma pack() #pragma pack(4) struct my_struct_7 { char a; //1 char b[3]; //3 char c; //1 int d; //补齐 3 +4 }; #pragma pack() #pragma pack(4) struct test { char x1; //1 short x2; //补齐1+ 2 float x3; //4 char x4; //1 补齐+3 }; #pragma pack() int main() { cout<<"char:"<<sizeof(char)<<endl; cout<<"short:"<<sizeof(short)<<endl; cout<<"int:"<<sizeof(int)<<endl; cout<<"long:"<<sizeof(long)<<endl; cout<<"float:"<<sizeof(float)<<endl; cout<<"double:"<<sizeof(double)<<endl; cout<<"long double:"<<sizeof(long double)<<endl; cout<<sizeof(my_struct_1)<<endl;//8 cout<<sizeof(my_struct_2)<<endl;//16 cout<<sizeof(my_struct_3)<<endl;//14 cout<<sizeof(my_struct_4)<<endl;//16 cout<<sizeof(my_struct_5)<<endl;//14 cout<<sizeof(my_struct_6)<<endl;//5 cout<<sizeof(my_struct_7)<<endl;//12 cout<<sizeof(test)<<endl;//12 system("pause"); return 0; }
解析C语言中结构体struct的对齐问题 ↩
彻底搞清计算结构体大小和数据对齐原则 ↩
知识点总结——结构体大小、内存对齐方式 ↩
C语言字节对齐、结构体对齐最详细的解释 ↩
深入理解计算机系统原书第2版 ↩
https://blog.csdn.net/cclethe/article/details/79659590
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。