从单片机基础到程序框架(全集 2019pdf版).pdf - 第308页

{ unsigned l ong u32Data; //一个 un signed long 占 用 4 个字节。 unsigned c har u8Data; //一个 u nsigned ch ar 占用 1 个字节 。 }; struct S tructMould _1 GtMould_ 1; //占用多少个字 节内存呢? 分析 :更 改 u 8Data 和 u 32Data 的 位置 顺序 后 ,u3 2Data 在 前 u8Da…

100%1 / 836
为什么要对齐?单片机内部硬件层面一条指令处理的数据宽度是固定的,比如,因为一个字节 8 位,
所以,8 位的单片机一次处理的数据宽度是 1 个字节(8 除以 8 等于 1)16 位的单片机一次处理的数据宽
2 个字节(16 位除以 8 等于 2)32 位的单片机一次处理的数据宽度是 4 个字(32 位除以 8 位等 4)
如果字节不对齐,本来单片机一个指令能处理的数据可能就要分解成 2 个指令甚至更多的指令,所以 C 编译
器为了让单片机处于最佳状态,在某些情况就会涉及内存对齐,结构体就涉及到内存对齐。
结构体的内存对齐表现在哪里呢?请看下面两个例子:
第一个例子:8 位单片机
struct StructMould_1 //“造模”
{
unsigned char u8Data; //一个 unsigned char 占用 1 个字节
unsigned long u32Data; //一个 unsigned long 4 个字节。
};
struct StructMould_1 GtMould_1; //占用多少个字节内存呢?
分析:GtMould_1 个变量占用多少个内存字节呢?假 GtMould_1 首地址是 0,那么地 0 存放
u8Data,u8Data 1 个字下来 1(0+1)了, 1 4
个字节的成 u32Data 吗?因为 8 位单片机的对齐倍数” 1(8 除以 8)那么地址 1 显然是可以整除“对
齐倍数”1 的,因此地址 1 是可以果断存储 u32Data 成员的。因此GtMould_1 占用的总字节数是 5(1+4)
也就是 u8Data u32Data 两者所占字节数之和。
第二个例子:32 位单片机。
struct StructMould_1 //“造模”
{
unsigned char u8Data; //一个 unsigned char 占用 1 个字节
unsigned long u32Data; //一个 unsigned long 4 个字节。
};
struct StructMould_1 GtMould_1; //占用多少个字节内存呢?
分析:GtMould_1 个变量占用多少个内存字节呢?假 GtMould_1 首地址是 0,那么地 0 存放
成员 u8Data,u8Data 占用 1 个字节,所以接下来的地址是 1(0+1)那么问题来了,地址 1 能直接存放占
4 个字节的成 u32Data ?不能。因 32 单片机的“对齐倍数” 4(32 8),那么地址 1 显然是
不可以整除“对齐倍数”4 的,因此,就要把地址 1 更改挪到地 4 这里才符合“地址对齐这样,就意味
着多插入了 3 “填充的字节因此GtMould_1 占用的总字节数是 8(1+3+4)也就“1 个字 u8Data,
3 个填充字节,4 u32Data”三者所占字节数之和。那么问题又来了如果把结构体内部成员 u8Data u32Data
的位置顺序更改一下,内存容量会有所改变吗?位置顺序更改后如下。
struct StructMould_1 //“造模”
{
unsigned long u32Data; //一个 unsigned long 4 个字节。
unsigned char u8Data; //一个 unsigned char 占用 1 个字节
};
struct StructMould_1 GtMould_1; //占用多少个字节内存呢?
分析:更 u8Data u32Data 位置顺序,u32Data u8Data 在后,GtMould_1 这个变量用多
少个内存字节呢?假设 GtMould_1 的首地址是 0,那么地址 0 就存放成员 u32Data,u32Data 占用 4 个字节,
所以接下来的地址是 4(0+4)那么问题来了,地址 4 能直接存放占用 1 个字节的成 u8Data 吗?能。
32 位单片机的“对齐倍数”是 4(32 除以 8)那么地址 4 显然是可以整除“对齐倍数”4 的,因此,地址 4
是可以果断存储 u8Data 的。那么,是不 GtMould_1 占用 5 个字节呢?不是。因为结构体的内存对齐,
还包括另外一条规定,那就是“一个结构体变量所占的内存总容量必须能整除该单片机的“对齐倍数
片机的位数除以 8)如果不能,C 编译器就会擅自在最后一个成员的后面插入若干个“填充字节”来满足这
个规则”根据这条规定,计算所得的总容量 5 是不能整除“对齐倍数”4 必须再额外填充 3 个字节补足
8,才能整除“对齐倍数”4,因此,更改顺序后,GtMould_1 还是占用 8 个字节(4+1+3),前 4 个字节是
u32Data,中间 1 个字节是 u8Data,后 3 个字节是“填充字节”
因为本教程采用的是 8 位的 51 内核单片机因此在上述这个例子中,GtMould_1 所占的字节数是符合
“第一个例子的情况,也就是占用 5 字节。内存对齐是遵守几条严格的规则的我只列出其中最关键的
两条给大家大致阅读一下,有一个印象即可,不强求死记硬背,只需知道“结构体因为存在内存对齐所以
实际内存容量是有可能大于内部各成员类型字节数相加之和,尤其 16 位或者 32 位这类单片机”就可以了。
(1)结构体内部某个成员相对结构体首地址的偏移地址必须能整除该单片机的“对齐倍数”(单
片机的位数除以 8),如果不能,C 编译器就会擅自在各成员之间插入若干个“填充字节”来满足这个规则。
(2)一个结构体变量所占的内存总容量必须能整除该单片机的“对齐倍数”单片机的位数除以
8),如果不能,C 编译器就会擅自在最后一个成员的后面插入若干个“填充字节”来满足这个规则。
【71.3 如何获取某个结构体变量的内存容量?】
结构体存在内存对齐的问题,就说明它的内存占用情况不会像普通数组那样一目了然那么,我们编写
程序的时候怎么知道某个结构体变量占用了多少个字节数?答案是:用 sizeof 宏函数。比如:
struct StructMould_1
{
unsigned long u32Data;
unsigned char u8Data;
};
struct StructMould_1 GtMould_1;
unsigned long a; //此变量用来获取结构体变 GtMould_1 所占用的字节总数
void main() //主函
{
a=sizeof(GtMould_1); //利用宏函数 sizeof 获取结构体变量所占用的字节总
}
【71.4 结构体之间的赋值。
结构体之间的赋值有两种,第一种是成员之间“一对一”的赋值,第二种是整个结构体之间“面对面”
的整体赋值。第一种成员赋值像普通变量赋值那样,没有那么多套路和忌讳,数据传递安全可靠。第二种整
个结构体之间赋值在编程体验上带有“一键操作”的快感,但是要注意避开一些“雷区”,首先,整体赋值
的前提是必须保证两个结构体变量都是同一个“结构体模板”造出来的变量,不同“模板”的结构体变量之
间禁止“整体赋值,其次,哪怕是“同一个模板”的结构体变量,也并不是所有的“同模板结构体”变
都能实现整个结构体之间的直接赋值,只有在结构体内部成员比较简单的情况下才适合“整体赋值,如果
结构体内部包含有“指针”或者“字符串”或者“其它结构体中的结构体这类情况就比较复杂,这时
议大家绕开有“雷区“整体赋值”而直接选用安全可靠的“成员赋值”什么是成员赋值”什么是
体赋值”?请看下面两个例子。
第一种:成员赋值。把结构体变量 GtMould_2_A 赋值 GtMould_2_B。
struct StructMould_2 //“造模”
{
unsigned long u32Data;
unsigned char u8Data;
};
struct StructMould_2 GtMould_2_A; //生成第 1 个结构体变量
struct StructMould_2 GtMould_2_B //生成第 2 个结构体变
void main() //主函
{
//先给 GtMould_2_A 赋初值
GtMould_2_A.u32Data=1;
GtMould_2_A.u8Data=2;
//通过“成员赋值”,把结构体变量 GtMould_2_A 赋值 GtMould_2_B。
GtMould_2_B.u32Data=GtMould_2_A.u32Data; //成员之间“一对一”的赋
GtMould_2_B.u8Data=GtMould_2_A.u8Data; //成员之间“一对一”的赋值
}
第二种:整体赋值。把结构体变量 GtMould_2_A 赋值 GtMould_2_B。
struct StructMould_2 //“造模”
{
unsigned long u32Data;
unsigned char u8Data;
};