当前位置: 首页 > 科技观察

说说Linux的字节对齐

时间:2023-03-18 16:09:59 科技观察

最近一口君在做项目,遇到了一个问题。运行在ARM上的threadx是使用消息队列与DSP通信传递消息(最终实现原理是中断+共享)在实际运行过程中,发现threadx总是死机,所以查了一下,是因为消息传递的结构不考虑字节对齐问题,整理出C语言中的字节对齐问题,分享给大家1.概念对齐与数据在内存中的位置有关,如果一个变量的内存地址恰好是其的整数倍它的长度,就说它是自然对齐的。比如在32位CPU下,如果一个整型变量的地址是0x00000004,那么它就是自然对齐的。首先要明白什么是位、字节、字long。现代计算机的字长通常为16、32或64位。(一般N位系统的字长为N/8字节。)不同的CPU一次可以处理不同的数据位。32位CPU可以处理3一次2位数据,64位CPU一次可以处理64位数据。这里的位是指字长。所谓字长,我们有时称之为字(word)。在16位CPU中,一个字恰好是两个字节,而在32位CPU中,一个字是四个字节。如果以字为单位,向上还有双字(twowords)、四字(quadrupleword)。2.对齐规则对于标准数据类型,其地址只需为其长度的整数倍,非标准数据类型按照以下原则进行对齐:  数组:按照基本数据类型对齐,第一个是对齐的,后面自然是对齐的。Union:按其包含的最大长度数据类型对齐。结构:结构中的每个数据类型都必须对齐。3.如何限制固定字节对齐的个数?1.默认默认情况下,C编译器根据其自然边界条件为每个变量或数据单元分配空间。通常,可以通过以下方法改变默认的对齐条件:2.#pragmapack(n)·使用伪指令#pragmapack(n),C编译器将按照n字节对齐。·使用指令#pragmapack()取消自定义字节对齐。#pragmapack(n)用于设置变量对齐n字节。N字节对齐是指变量存储起始地址的偏移量有两种情况:如果n大于等于变量占用的字节数,那么偏移量必须满足默认对齐方式;如果n小于变量的类型占用的字节数,那么偏移量是n的倍数,不需要默认对齐。结构的总大小也有约束。如果n大于等于所有成员变量类型占用的字节数,那么结构体的总大小必须是空间占用最大的变量占用空间的倍数;否则它必须是n的倍数。3.__attribute此外,还有一种方式如下:·__attribute((aligned(n))),让作用结构体成员在n字节的自然边界上对齐。如果结构中任意成员的长度大于n,则按照最大成员的长度对齐。·attribute((packed)),取消编译时结构体的优化对齐,按照实际占用字节数对齐。4.汇编.align汇编代码通常使用.align来指定对齐的字节数。.align:用于指定数据的对齐方式,格式如下:.align[absexpr1,absexpr2]以一定的对齐方式填充未使用的存储区。第一个值表示对齐方式,4、8、16或32。第二个表达式值表示填充值。4、为什么需要对齐?操作系统不是逐字节访问内存,而是按2、4、8等字长访问内存。因此,CPU从内存读取数据到寄存器时,IO的数据长度通常为字长。例如,32位系统的访问粒度为4字节,64位系统为8字节。当要访问的数据长度为n字节,数据地址对齐n字节时,操作系统可以一次性高效定位数据,不需要多次读取、处理对齐操作等额外操作。数据结构应尽可能在自然边界上对齐。如果访问未对齐的内存,CPU需要进行两次内存访问。字节对齐可能带来的隐患:代码中很多关于对齐的隐患都是隐含的。例如,在强制类型转换的情况下。例如:unsignedinti=0x12345678;unsignedchar*p=NULL;unsignedshort*p1=NULL;p=&i;*p=0x00;p1=(unsignedshort*)(p+1);*p1=0x0000;的最后两行代码,从Odd边界访问unsignedshort类型的变量,显然不符合对齐要求。在x86上,类似的操作只会影响效率,但在MIPS或sparc上,可能会报错,因为它们需要字节对齐。5、例1:os基本数据类型占用的字节数首先查看运行系统的位数查看64位操作系统下基本数据类型占用的字节数:#includeintmain(){printf("sizeof(char)=%ld\n",sizeof(char));printf("sizeof(int)=%ld\n",sizeof(int));printf("sizeof(float)=%ld\n",sizeof(float));printf("sizeof(long)=%ld\n",sizeof(long));printf("sizeof(longlong)=%ld\n",sizeof(longlong));printf("sizeof(double)=%ld\n",sizeof(double));return0;}例2:结构体占用的内存大小——默认规则考虑占用的位数通过以下结构structyikou_s{doubled;charc;inti;}yikou_t;执行结果sizeof(yikou_t)=16ineachvariableinthecontent位置关系如下:成员C的位置也受字节序的影响,有的可能在位置8,编译器已经帮我们进行了内存对齐,以及每个成员变量存储的起始地址相对于结构体起始地址的偏移量大小必须是变量类型占用的字节数的倍数,结构体的大小必须是结构中占用最大空间的类型占用的字节数。对于偏移量:变量类型n的起始地址相对于结构体起始地址的偏移量必须是sizeof(type(n))的倍数结构体大小:必须是成员最大类型字节的倍数char:offset量必须是sizeof(char)的倍数int:offset必须是sizeof(int)的倍数,是4的倍数float:offset必须是sizeof(float)的倍数,是4的倍数of4double:偏移量必须是sizeof(double)是8的倍数。例3:调整结构体的大小。我们调整变量在结构体中的位置如下:如下:当结构体中有嵌套成员时,复合成员相对于结构体首地址的偏移量为最宽基本类型的整数倍复合成员。示例4:#pragmapack(4)#pragmapack(4)structyikou_s{charc;doubled;inti;}yikou_t;sizeof(yikou_t)=16示例5:#pragmapack(8)#pragmapack(8)structyikou_s{charc;doubled;inti;}yikou_t;sizeof(yikou_t)=24示例六:汇编代码示例:下面是截获的uboot代码中异常向量irq和fiq的入口位置代码:给大家示例:#includemain(){structA{inta;charb;shortc;};structB{charb;inta;shortc;};structAA{//inta;charb;shortc;};structBB{charb;//inta;shortc;};#pragmapack(2)/*指定2字节对齐*/structC{charb;inta;shortc;};#pragmapack()/*取消指定对齐,恢复默认对齐*/#pragmapack(1)/*指定1字节对齐*/structD{charb;inta;shortc;};#pragmapack()/*取消指定对齐,恢复默认对齐*/ints1=sizeof(structA);ints2=sizeof(structAA);ints3=sizeof(structB);ints4=sizeof(structBB);ints5=sizeof(structC);ints6=sizeof(structD);printf("%d\n",s1);printf("%d\n",s2);printf("%d\n",s3);printf("%d\n",s4);printf("%d\n",s5);printf("%d\n"n",s6);}本文转载自微信公众号“一口Linux”,你可以通过以下二维码关注它。转载本文请联系易口Linux公众号。