在介绍float型数据的处理和发送之前,先介绍一下大端和小端以及联合体的大小分析。
一.什么是大端小端?如何测试你的CPU是大端还是小端?
1.大端小端:
小端:采用小端模式的CPU对操作数的存放方式是从低字节到高字节;
大端:采用大端模式的CPU对操作数的存放方式是从高字节到低字节。
高低字节:如0x12345678,其中78是低位,12是高位,就像十进制1234,4是个位,1是千位。
换个说法:
小端:数据低位放在了内存低地址,数据高位放在了内存高地址;
大端:数据低位放在了内存高地址,数据高位放在了内存低地址。
用一张图形象的表示下,地址从低到高,小端的低位放在了低地址,大端的低位放在了高地址。
2.如何测试你的CPU是大端还是小端?
代码:
void checkCPU() {
union test {
int a;
char b;
}c;
c.a = 0x1234;//大端:12,34;小端:34,12
if (c.b == 0x34)
cout << "小端" << endl;
else
cout << "大端" << endl;
cout << "The size of union test is : " << sizeof(test) << "bytes" << endl;
}
测试发现运行这个程序的设备是小端存储,同时会发现test这个union占用了四个字节,对联合体的大小分析,下文详细说明。
二.联合体union大小分析
1.联合体大小分析:
代码:
void size(void)
{
typedef union DATA
{
double i;
int j[5];
char k;
}data;
struct Month
{
double i;
int j[5];
char k;
};
struct Year
{
int i ;
data j ;//union类型
double k ;
};
cout << "The size of union DATA is : " << sizeof(DATA) << "bytes" << endl;
cout << "The size of struct Month is : " << sizeof(Month) << "bytes" << endl;
cout << "The size of struct Year is : " << sizeof(Year) << "bytes" << endl;
}
输出结果:
结果和你预测的一样吗?
提到了union(联合体),就不得不提它的好兄弟struct(结构体)
union和struct比较
这个问题也常在面试中出现,其他面试问题,可以点击下方链接。
-
相同点:二者都是常见的复合结构,都是由多个不同的数据类型成员组成;
-
不同点:联合体中所有的成员公用一块地址空间,即联合体只存放一个被选中的成员,内存空间是最长成员占用的空间,需要进行字节对齐。
结构体所有成员占用空间是累加的,其所有成员都存在,不同成员会存在不同的地址,内存空间等于所有成员占用的空间之和,同样需要字节对齐。
字节对齐:
基本概念:许多计算机系统对基本数据类型合法地址做出了一些限制,要求某种类型对象的地址必须是某个值K(通常是2,4或8)的倍数。这种对齐限制简化了形成处理器和存储器系统之间的接口的硬件设计。
为什么要字节对齐?
需要字节对齐的根本原因在于CPU访问数据的效率问题。例如,假设一个处理器总是从存储器中取出8个字节,则地址必须为8的倍数。如果我们能保证将所有的double类型数据的地址对齐成8的倍数,那么就可以用一个存储器操作来读或者写值了。否则,我们可能需要执行两次存储器访问,因为对象可能被分放在两个8字节存储块中。
如何处理字节对齐?
简单来说:对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
联合体 :按其包含的长度最大的数据类型对齐。
结构体: 结构体中每个数据类型都要对齐。
具体分析:
64位机器下,double占8字节、int占4字节、char占1字节。union里面最大的变量类型时候int[5],它的大小是4*5=20字节,由于union中double占了8字节,因此union要8字节对齐,所占的空间是8的倍数。为了实现8字节对齐,所占的空间为24。
struct Month中每个变量占用的空间依次为:8+20+1=29,进行8字节对齐,所以最终占用的字节长度是32。
struct Year中每个变量占用的空间依次为:4+24+8=36,进行8字节对齐,占用空间为40。注意这里 union data已经进行字节对齐。
2.联合体存放顺序:
1、union中可以定义多个成员,union的大小由最大的成员的大小决定。
2、union成员共享同一块大小的内存,一次只能使用其中的一个成员。
3、对某一个成员赋值,会覆盖其他成员的值(也不奇怪,因为他们共享一块内存。但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,比如对char成员赋值就不会把整个int成员覆盖掉,因为char只占一个字节,而int占四个字节)
4、**联合体union的存放顺序是所有成员都从低地址开始存放的。**前文提到小端设备低地址存放的是低字节。
预测一下下面代码的输出结果
代码:
void test2(void)
{
union test
{
int i;
short d;
char ch;
}c;
c.i = 0x12345678;
c.d = 0xabcd;
c.ch = 0xff;
printf("%x\n", c.i);
}
结果:
结果和你预想的一样吗?
分析一下,三个变量在内存中的存放位置如下:
后面赋值的变量会把前面的覆盖住,所以最终输出的是:1234abff
三.串口通信float型数据的处理和发送
在做下位机通信时往往会用到串口,包括下位机将数据传输给上位机,或者是下位机与下位机之间进行数据传输,这时候就会遇到发送数据的问题。单片机通过串口发送数据时往往是一次一个字节(8位),如果传输char(8位)型数据则很好办,只需要直接发送就可以了。但是在发送int型数据和 float型数据时就会稍微有些复杂。
当发送int型或long型数据时还比较简单,一个int型数据是16位(32位系统),long是32位,把int型/long型数据变成2/4个char型数据发送出去就可以了。
程序如下:
void long_char(unsigned long l, unsigned char*s)
{
*s = l >> 24;
*(s+1) = l >> 16;
*(s+2) = l >> 8;
*(s+3) = l;
}
在串口助手上就可以接收到相应的long型数据了。
当发送float型数据时稍微有些复杂。下面简单介绍下float型数据在内存中的存储方式(double类似)。
浮点数的表示存储方式:
32位机中,一个double类型占8个字节,也就是说data需要拆分8块通过UART发送。
这时候要么采取上述发送int型数据,利用指针的方法,但是太过繁琐。我们通常采取联合体(共同体)的方式。
举个栗子:
#include <iostream>
#define MAX_LENTH 8
union U1
{
char s[MAX_LENTH];
double d;
};
union U2
{
char s[MAX_LENTH];
double d;
};
int main()
{
U1 u1;
U2 u2;
int i = 0;
u1.d = 2.111;
u2.d = 3.00;
printf("u1.d = %lf\n", u1.d);
printf("u2.d = %lf\n", u2.d);
printf("Send Data...\n");
for (i = 0; i < MAX_LENTH; i++)
{
u2.s[i] = u1.s[i];
}
printf("u2.d = %lf\n", u2.d);
return 0;
}
运行结果:
四.结构体的定义方式
1.结构的定义
//结构的定义
//定义一个结构的一般形式为:
struct 结构名
{
成员表列
}
//成员表由若干个成员组成,每个成员都是该结构的一个组成部分。
//对每个成员也必须作类型说明。
例如:
struct stu
{
int num;
char name[20];
int age;
}
2.结构体变量的定义
说明结构变量有以下三种方法。以上面定义的stu为例来加以说明
1.先定义结构,再说明结构变量。 如:
struct stu
{
int num;
char name[20];
int age;
};
struct stu boy1,boy2;
2.在定义结构类型的同时说明结构变量。例如:
struct stu
{
int num;
char name[20];
int age;
}boy1,boy2;
3.直接说明结构变量。
struct
{
int num;
char name[20];
int age;
}boy1,boy2;
使用结构变量成员的一般形式是:
结构变量名**.**成员名
例如:
boy1.num
//即第一个人的学号
3.嵌入式开发经常这样使用
typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef; //通过typedef给结构体起个“别名”
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
欢迎关注公众号,干货满满。