最近调试一段通讯程序代码,因为协议都是定好的,就想着直接通过memcpy,将接收的字节数据直接转换为结构体类型,但实际转换时发现拷贝的结构体数据不对,后来找了半天发现时结构体未对齐导致的数据获取失败,下面使用测试代码把这一问题复现一下。
问题场景复现
下面就是根据实际问题简化写的一个程序
#include <stdio.h>
#include <string.h>
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef struct tagMessage
{
uint8 head;
uint16 cmd;
uint8 len;
uint8 data[8];
}Message_T;
Message_T tMessage;
uint8 Rcv_Data[12] = {0x55,0x22,0x01,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};
int main()
{
memcpy(&tMessage, Rcv_Data, sizeof(tMessage));
printf("结构体成员数值:0x%X ,0x%X ,0x%X \n", tMessage.head, tMessage.cmd, tMessage.len);
printf("************数据***************\n");
for (uint8 i=0 ;i<8 ;i++)
{
printf("0x%X ", tMessage.data[i]);
}
printf("\n*******************************");
}
将上述代码进行打印输出
结构体成员数值:head=0x55 ,cmd=0x801 ,len=0x1
************数据***************
0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x0
*******************************
神奇的事情发生了,我的0x22呢,神秘消失了,这是咋回事,难道拷贝出错了?
问题分析
这是就需要把我们的内存窗口调出来看下,到底是数据拷贝出错还是其他原因导致的数据解析异常。
由内存窗口可以看到,数据拷贝是没问题的,那就奇怪了,难道找错地方了,本来想找秋香,结果进了如花的门。为了验证这个问题,增加结构体成员地址的打印。下面看输出结果。
首地址没问题,cmd的地址应该是0x21A501,结果确实0x21A502,怪不得数据解析错位。memcpy是按照结构体首地址进行数据存储,而结构体成员变量的地址并非是按照其自身数据大小去进行地址分配,这里面就涉及到结构体对齐的问题。
这篇文章对结构体对齐有比较详细的介绍《结构体对齐》,这里就不在赘述,简单来说就是结构体成员的存储占用受到其前后成员和编译器设置(vs默认8字节对其)的影响,如上述程序,第一个成员head,uint8类型,占一个字节,地址 0x85A500;第二个成员cmd,uint16类型,占两个字节,就必须要对其存储到结构体变量偏移为2倍数的地址处,地址:0x85A502;这就导致数据解析时,数据不对的情况发生。
解决方法
针对上述问题,只需要在结构体前面加上#pragma pack(1) 结尾加#pragma pack() ,按照实际需求设定好当前的结构体对其大小即可。
#pragma pack(1) // 将内存布局设置为1字节对齐
typedef struct tagMessage
{
uint8 head;
uint16 cmd;
uint8 len;
uint8 data[8];
}Message_T;
#pragma pack()
打印输出测试
结构体成员数值 head:headaddr=0x7EA500 ,cmdval=0x55
结构体成员数值 cmd: cmdaddr=0x7EA501 ,cmdval=0x122
结构体成员数值 len: lenaddr=0x7EA503 ,cmdval=0x8
************数据***************
0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8
由上可以看到,当前的数据获取结果正常。
后记
上述的结果还有个小问题,不知各位看官有没有发现,就是测试数据的cmd:22 01 ,但实际解析的确实01 22,这就要涉及到另外一个问题了,大小端存储,后面有空的时候再来说这个问题吧,今天先到这。
如有问题,还望指出,感谢!要是觉得文章对你有帮助,麻烦一键三连哈😊😊😊!!!