文章的知识点来源于:https://www.cnblogs.com/heart-flying/p/9556401.html,我在原文提供的方法上自己做了一些验证以及思考,以作为自己的学习记录,方便日后回顾。这篇文章讲得简单明了,解决了我很久以来的疑惑,因此万分感谢,如有侵权的地方请私信我删除。
1、测试平台
本次实验的平台以32位CortexM0单片机,不同的平台的实验结果可能是不一样的,但是思路都一样。
2、基础概念
先介绍三个概念:自身对齐值、指定对齐值、有效对齐值。
自身对齐值:数据类型本身的对齐值,例如char类型的自身对齐值是1,short类型是2,int类型是4;
指定对齐值:编译器或程序员指定的对齐值,32位单片机的指定对齐值默认是4;
有效对齐值:自身对齐值和指定对齐值中较小的那个。
对齐有两个规则:
1、不但结构体的成员有有效对齐值,结构体本身也有对齐值,这主要是考虑结构体的数组,对于结构体或者类,要将其补齐为其有效对齐值的整数倍。而结构体的有效对齐值是其最大数据成员的自身对齐值;
2、存放成员的起始地址必须是该成员有效对齐值的整数倍。
3、开始测试
先举几个例子,假设以下所有的结构体的起始地址都是0x0000 0000
#pragma pack(4)
typedef struct A
{
u8 a[1]; //自身对齐是1,指定对齐是4,因此有效对齐是1, 0地址存放a
u8 b[1]; //自身对齐是1,指定对齐是4,因此有效对齐是1, 1地址存放b
u8 c[1]; //自身对齐是1,指定对齐是4,因此有效对齐是1, 2地址存放c
u8 d[1]; //自身对齐是1,指定对齐是4,因此有效对齐是1, 3地址存放d
u8 e[1]; //自身对齐是1,指定对齐是4,因此有效对齐是1, 4地址存放e 此时结构体的长度是5字节
//最后整个结构体长度进行对齐,结构体的有效对齐值是其最大数据成员的自身对齐值
//最大数据成员是1字节,因此最后结构体本身长度要进行1字节对齐,因此结构体总长度是5字节
}structA;
#pragma pack()
#pragma pack(4)
typedef struct B
{
u8 a[1]; //自身对齐是1,指定对齐是4,因此有效对齐是1, 0地址存放a
u16 b[1]; //自身对齐是2,指定对齐是4,因此有效对齐是2, 1地址进行填充,2、3地址存放b(思考题1:2地址存放高字节还是3地址存放高字节?)
u8 c[1]; //自身对齐是1,指定对齐是4,因此有效对齐是1, 4地址存放c
u8 d[1]; //自身对齐是1,指定对齐是4,因此有效对齐是1, 5地址存放d
u8 e[1]; //自身对齐是1,指定对齐是4,因此有效对齐是1, 6地址存放e 此时结构体的长度是7字节
//最后整个结构体长度进行对齐,结构体的有效对齐值是其最大数据成员的自身对齐值
//最大数据成员是2字节,因此最后结构体本身长度要进行2字节对齐,因此结构体总长度是8字节
}structB;
#pragma pack()
#pragma pack(4)
typedef struct C
{
u8 a[1]; //自身对齐是1,指定对齐是4,因此有效对齐是1, 0地址存放a
u32 b[1]; //自身对齐是4,指定对齐是4,因此有效对齐是2, 1、2、3地址进行填充,4、5、6、7地址存放b
u8 c[1]; //自身对齐是1,指定对齐是4,因此有效对齐是1, 8地址存放c
u8 d[1]; //自身对齐是1,指定对齐是4,因此有效对齐是1, 9地址存放d
u8 e[1]; //自身对齐是1,指定对齐是4,因此有效对齐是1, 10地址存放e 此时结构体的长度是11字节
//最后整个结构体长度进行对齐,结构体的有效对齐值是其最大数据成员的自身对齐值
//最大数据成员是4字节,因此最后结构体本身长度要进行4字节对齐,因此结构体总长度是12字节
}structC;
#pragma pack()
4、测试代码
structA A;
structB B;
structC C;
volatile u32 len_A, len_B, len_C;
void align_test(void)
{
len_A = sizeof(A);
len_B = sizeof(B);
len_C = sizeof(C);
A.a[0] = B.a[0] = C.a[0] = 1;
A.b[0] = B.b[0] = C.b[0] = 2;
A.c[0] = B.c[0] = C.c[0] = 3;
A.d[0] = B.d[0] = C.d[0] = 4;
A.e[0] = B.e[0] = C.e[0] = 5;
}
5、测试结果
通过MDK的在线调试可以清楚地看到结构体这几个结构体的地址以及它的成员的地址,用sizeof(xxx)可以计算结构体的总大小,可以看出来我们以上的分析是正确的。
同时我们也能看到,所有的由编译器自己分配的结构体的首地址,一定是4字节对齐的,例如结构体A的首地址是0x100040F4,结构体B的首地址是0x100040FA,结构体C的首地址是0x10004B18,因此不用担心结构体的首地址的字节对齐问题,而只需要关心结构体成员和结构体总长度的字节对齐问题。
6、思考题
思考题1:结构体B中,2地址存放高字节还是3地址存放高字节?
这里涉及到大小端的问题,对于小端的MCU,高地址存放高字节,低地址存放低字节,例如本试验中,0x0002存放在2、3地址中,3地址存放0x00,2地址存放0x02,请看下图。
如果是大端的MCU,高地址存放低字节,低地址存放高字节,结果就会反过来,这也是编程中常常需要留意的问题了。