举个栗子
在正式开始前,我们先通过一个简单的栗子,理解一个知识点,这里先卖下关子,可以慢慢往下看.
有如下代码块:
typedef struct {
char a;
int b;
short c;
}CASE1;
上述结构体CASE1中包含:
- 1个字节长度的char类型的a;
- 4个字节长度的int类型的b;
- 2个字节长度的short类型的c.
不考虑其他因素的话,CASE1占用的空间应该是7个字节.
但是因为编译器要对数据成员在空间上进行对齐,由于结构体自身对齐值取数据成员中自身对齐值的最大值,即取4,并且0x00~0x0C的12字节空间满足12 % 4 = 0,所以sizeof(struct CASE1)的值为12.
那么这到底是为什么呢?甩锅给编译器,咱就不管了吗?嗯,还真不懂!好的本篇文章到此结束!@!
能不能讲点能听懂的呢?emmm…那就从另一个知识点来理解下这个原理吧.
C语言的结构体对齐原则
- 数据成员对齐规则
结构体的数据成员中,第一个成员从offset为0的地址开始,以后每一个成员存储的起始地址为该成员大小的整数倍(win32中int为4字节对齐). - 结构体作为成员
如果一个结构体b作为结构体a的数据成员,则在结构体a中的结构体1要从内部成员最大的整数倍地址开始存储. - 结构体的总大小
结构体的总大小为该结构体内部最大基本类型的整数倍,不足的要补齐,而不是所有成员的大小总和.
在理解了结构体对齐原则之后,我们使用原则1和原则3就可以轻松的计算出,CASE1的长度为12.
那么再取一个栗子:
typedef struct {
char a;
short b;
int c;
}CASE2;
如果没有搞懂上述的三个原则,那么必会有人认为sizeof(struct CASE2)的大小理解为12,但是通过原则1和原则3,我们可以清晰的计算出,CASE2的大小为8.
我们在stm32中运行一下代码测试一下:
//这里测试的是第一个栗子啊
typedef struct {
char a;
int b;
short c;
}CASE2;
int main(void ){
CASE2 Case;
printf("&Case.a %d\n", (unsigned int )(void *)&Case.a);
printf("&Case.b %d\n", (unsigned int )(void *)&Case.b);
printf("&Case.c %d\n", (unsigned int )(void *)&Case.c);
return 0;
}
得到的结果是
&Case.a 536873208
&Case.b 536873212
&Case.c 536873216
可以看出,结果和我们所分析对齐原则是一致,那当我知道了结构体的数据是如何存储的时候,还怕不好解析数据吗?
结构体对齐在协议解析中的用法
干货来了!!!
(点题了)
再举一个栗子:
假设有如下的协议:
帧头 | 设备地址 | 命令字 | 参数1 | 参数2 | 帧尾 |
---|---|---|---|---|---|
EF | 68 |
协议由帧头、设备地址、命令字、参数1/2及帧尾组成,假设每个参数都为1个字节固定长度,那么我们可以建立如下数据结构:
typedef struct{
uint8_t frame_head;
uint8_t frame_addr;
uint8_t frame_cmdtype;
uint8_t frame_param1;
uint8_t frame_param2;
uint8_t frame_tail;
}Struct_Frame;
当设备接收完一整包数据时,可使用如下方法进行解析:
int frame_decode(void *buff){
if(buff == NULL)
return -1;
Struct_Frame *frame = (Struct_Frame buff);
printf("frame_head = %d\r\n", frame->frame_head);
printf("frame_addr = %d\r\n", frame->frame_addr);
//...
return 0;
}
若此时设备接收到的数据是0XEF1201020368的话,此时调用frame_decode便可以轻松的将数据解析出来.
结果为:
frame_head = EF
frame_addr = 12
...
这种方法对于解析定长数据非常方便,但前提是对数据的组成及排序非常清楚,对于不定长协议的解析,嗯嗯,暂时还没想出什么特别好的方法,不过方法肯定有,只是我没发现,以后发现方法的话,会再写篇小作文.