当前位置:文档之家› stm32中使用#pragma pack(非常有用的字节对齐用法说明)

stm32中使用#pragma pack(非常有用的字节对齐用法说明)

#pragma pack(4) //按4字节对齐,但实际上由于结构体中单个成员的最大占用字节数为2字节,因此实际还是按2字节对齐typedef struct{char buf[3];//buf[1]按1字节对齐,buf[2]按1字节对齐,由于buf[3]的下一成员word a是按两字节对齐,因此buf[3]按1字节对齐后,后面只需补一空字节word a; //#pragma pack(4),取小值为2,按2字节对齐。

}kk;#pragma pack() //取消自定义字节对齐方式对齐的原则是min(sizeof(word ),4)=2,因此是2字节对齐,而不是我们认为的4字节对齐。

这里有三点很重要:1.每个成员分别按自己的方式对齐,并能最小化长度2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度3.对齐后的结构体整体长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐补充一下,对于数组,比如:char a[3];这种,它的对齐方式和分别写3个char是一样的.也就是说它还是按1个字节对齐.如果写: typedef char Array3[3];Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度.不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个.声明:整理自网络达人们的帖子,部分参照MSDN。

作用:指定结构体、联合以及类成员的packing alignment;语法:#pragma pack( [show] | [push | pop] [, identifier], n )说明:1,pack提供数据声明级别的控制,对定义不起作用;2,调用pack时不指定参数,n将被设成默认值;3,一旦改变数据类型的alignment,直接效果就是占用memory的减少,但是performance会下降;语法具体分析:1,show:可选参数;显示当前packing aligment的字节数,以warning message的形式被显示;2,push:可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈;3,pop:可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment 数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop 直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略;4,identifier:可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack 中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作;5,n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。

重要规则:1,复杂类型中各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个类型的地址相同;2,每个成员分别对齐,即每个成员按自己的方式对齐,并最小化长度;规则就是每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐;3,结构体、联合体或者类的数据成员,第一个放在偏移为0的地方;以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度两个中比较小的那个进行;也就是说,当#pragma pack指定的值等于或者超过所有数据成员长度的时候,这个指定值的大小将不产生任何效果;4,复杂类型(如结构体)整体的对齐是按照结构体中长度最大的数据成员和#pragma pack指定值之间较小的那个值进行;这样当数据成员为复杂类型(如结构体)时,可以最小化长度;5,复杂类型(如结构体)整体长度的计算必须取所用过的所有对齐参数的整数倍,不够补空字节;也就是取所用过的所有对齐参数中最大的那个值的整数倍,因为对齐参数都是2的n次方;这样在处理数组时可以保证每一项都边界对齐;对齐的算法:由于各个平台和编译器的不同,现以本人使用的gcc version 3.2.2编译器(32位x86平台)为例子,来讨论编译器对struct 数据结构中的各成员如何进行对齐的。

在相同的对齐方式下,结构体内部数据定义的顺序不同,结构体整体占据内存空间也不同,如下:设结构体如下定义:struct A {int a; //a的自身对齐值为4,偏移地址为0x00~0x03,a的起始地址0x00满足0x00%4=0;char b; //b的自身对齐值为1,由于紧跟a之后的地址,即0x04满足0x04%1=0,所以b存放在0x04地址空间short c; //c的自身对齐值为2,由于紧跟b之后的地址0x05%2不是0,而0x06%2=0,因此c的存放起始地址为0x06,存放在0x06~0x07空间。

在b和c之间的0x05地址则补空字节。

};结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个。

所以A用到的空间应该是7字节。

但是因为编译器要对数据成员在空间上进行对齐。

由于结构体自身对齐值取数据成员中自身对齐值的最大值,即4,并且0x00~0x07的8字节空间满足8%4=0,所以sizeof(strcut A)值为8。

现在把该结构体调整成员变量的顺序。

struct B {char b; //b的自身对齐值为1,其起始地址为0x00,由于满足0x00%1=0,所以b存放在0x00地址空间int a; //a的自身对齐值为4,由于紧跟b之后的地址0x01%4不是0,而0x04%4=0,因此c的存放起始地址为0x04,存放在0x04~0x07空间。

在b和a之间的0x01~0x03地址则补3个空字节。

short c; //c的自身对齐值为2,由于紧跟a之后的地址0x08%2=0,因此c的存放起始地址为0x08,存放在0x08~0x09空间。

};这时候同样是总共7个字节的变量,但是由于结构体自身对齐值取数据成员中自身对齐值的最大值,即4,并且0x00~0x09的10字节空间不满足10%4=0,而12%4=0,所以sizeof(struct B)的值却是12,即在紧跟c之后的0x0A~0x0B地址还需补两个空字节,使得整个结构体占用的字节空间为12个字节。

下面我们使用预编译指令#pragma pack (value)来告诉编译器,使用我们指定的对齐值来取代缺省的。

#pragma pack (2) /*指定按2字节对齐,等价于#pragma pack(push,2)*/struct C {char b;int a;short c;};#pragma pack () /*取消指定对齐,恢复缺省对齐,等价于#pragma pack(pop)*/sizeof(struct C)值是8。

修改对齐值为1:#pragma pack (1) /*指定按1字节对齐*/struct D {char b;int a;short c;};#pragma pack () /*取消指定对齐,恢复缺省对齐*/sizeof(struct D)值为7。

对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,long类型,其自身对齐值为4,double,long long类型,其自身对齐值为8,单位字节。

这里面有四个概念值:1.数据类型自身的对齐值:就是上面交代的基本数据类型的自身对齐值。

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

3.结构体或者类的自身对齐值:其数据成员中自身对齐值最大的那个值。

4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。

有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。

有效对齐值N是最终用来决定数据存放地址方式的值,最重要。

有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。

第一个数据变量的起始地址就是数据结构的起始地址。

结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍,结合下面例子理解)。

这样就不难理解上面的几个例子的值了。

例子分析:分析例子B;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的自身对齐值为其变量中最大对齐值(这里是a)和指定对齐值(这里是4)中较小的那个,所以就是4,所以结构体的有效对齐值也是4。

根据结构体圆整的要求,0x0009到0x0000=10字节,(10+2)%4=0。

所以0x0000A到0x000B也为结构体B所占用。

故B 从0x0000到0x000B共有12个字节,sizeof(struct B)=12;同理,分析上面例子C:#pragma pack (2)/*指定按2字节对齐*/struct C {char b;int a;short c;};#pragma pack () /*取消指定对齐,恢复缺省对齐*/第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1=0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中,符合0x0002%2=0。

相关主题