从 Keil 中的 uint8_t 数组获取 uint16_t 值
Posted
技术标签:
【中文标题】从 Keil 中的 uint8_t 数组获取 uint16_t 值【英文标题】:Getting uint16_t value from uint8_t array in Keil 【发布时间】:2021-11-30 22:42:45 【问题描述】:我正在尝试在不使用“
uint8_t buffer[8] = 0x11,0x22,0x33,0x44;
uint16_t val = *((uint16_t *)buffer);
如果我在 keil 中尝试上面的代码,它就可以工作。当我在 struct 编译器中为数组尝试它时不会出错,但它会在运行时进入硬故障处理程序。
typedef struct
uint8_t address;
uint8_t opID;
uint8_t dataLen;
uint8_t data[250];
uint8_t crc[2];
MODBUS;
MODBUS receivedData;
uint16_t val = *((uint16_t *)receivedData.data);
我还在在线 c 编译器中尝试过这个(结构中的数组)。它工作没有任何问题。我应该怎么做才能在keil中使用相同的东西?
【问题讨论】:
目标 CPU 是什么? 它的STM32G031K8。 【参考方案1】:*((uint16_t *)buffer);
有两个未定义的行为错误:
buffer
可能未对齐,转换为更大的指针类型可能导致访问未对齐或硬件陷阱/异常。
代码违反了指针取消引用类型系统,即所谓的“严格别名违规”。 What is the strict aliasing rule? 这在嵌入式系统编译器中通常不是问题,但无论如何它仍然是未定义的行为,您不应该编写这样的代码。
此外,您还存在 UART 协议的网络字节序可能与 CPU 字节序不匹配的潜在问题。 What is CPU endianness? 大多数 UART 协议使用 Big Endian。
要解决此问题,您可以使用memcpy
,如另一个答案中所述。或者更有效地,一个包装器union
:
typedef union
uint8_t u8 [8];
uint16_t u16 [4];
uart_buf_t;
这解决了不对齐和严格的别名,但它不会解决潜在的不匹配字节序。要解决这个问题,您需要改用位移位。
【讨论】:
Union 很好,只要它是 C99 或更高版本,对于旧标准,它也是 UB。 @alagner 这部分标准(ISO 9899:2018 6.5.2.3/3)自 C90 以来没有改变。在 C99 中添加了一个信息丰富的脚注,但这无关紧要。脚注不规范。 @Lundin 它确实有效,但我有一个问题。当我使用union
并将字节复制到变量(MODBUS receivedData)时,它会丢失union
类型数据的第一个字节。假设我的缓冲区是 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08。当我复制它时,它变成这样:address=0x01,opID=0x02,dataLen=0x03,data[250]=0x05,0x06,0x07 ....。它只丢失第一个字节。如果我不在union
中使用 uint16_t,它可以复制所有内容而不会丢失任何字节。
@alig24 听起来你应该提出一个新问题,发布修改后的代码。
好吧,它仍然不能使用奇数字节作为 uint16_t 的第一个字节,我明白了。【参考方案2】:
使用memcpy
通常是处理此类转换的正确方法,因此:
uint16_t target;
uint8_t source[]=1,2,3,4;
memcpy(&target, source, sizeof(target));
要将较短的整数数组更改为较长的整数数组,您必须相应地对其进行修改:
uint16_t target[2]=0;
uint8_t source[4]=1,2,3,4;
memcpy(target, source, sizeof(target));
请记住相应地调整尺寸。
【讨论】:
【参考方案3】:注意:所讨论的目标是 Cortex®-M0+ 的变体,它支持未对齐的访问。因此,以下假设变得无关紧要。
硬故障最可能的原因是: 您正在使用不支持错位访问的核心。 例如,皮质 M0 变体。
建议:
一步一步调试,查看变量的内存位置,尤其是:
receivedData.data
尝试通过字节对齐访问该位置,检查是否会发生硬故障。
uint8_t val = *((uint8_t *)receivedData.data);
如果我猜对了,像下面这样更改结构定义,看看是否能解决问题。
typedef struct
uint8_t address;
uint8_t opID;
uint8_t dataLen;
uint8_t dummy_byte;
uint8_t data[250];
uint8_t crc[2];
MODBUS;
【讨论】:
重新排序成员比手动插入填充更好。无论如何,这不是错误的原因。未定义的行为强制转换是原因。 当我使用虚拟字节时它也可以工作。以上是关于从 Keil 中的 uint8_t 数组获取 uint16_t 值的主要内容,如果未能解决你的问题,请参考以下文章
将 unsigned char 数组转换为 uint8_t 数组?