从 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 数组?

使用 emscripten 如何将 C++ uint8_t 数组获取到 JS Blob 或 UInt8Array

c char 数组到 uint8_t 数组

以编程方式填充 uint8_t 数组

如何将 uint8_t 数组与 C 中的字符串进行比较?

切片一个 uint8_t 数组