为什么通过直接赋值复制结构会失败?

Why does copying a structure by a direct assignment fail?

我 运行 在将微控制器上的一些数据从一个结构复制到另一个结构时遇到硬故障异常。我尝试了不同的实现,它们应该做的都是一样的。查看我的代码行:

memcpy(&msg.data, data, 8);
memcpy(&msg.data, data, sizeof(*data));
memcpy(&msg.data, data, sizeof(msg.data));
msg.data = *data;  // Hard Fault

前三行工作得很好。最后一个以硬故障异常结束。 memcpy 行的汇编是相同的。直接分配的程序集不同:

  memcpy(&msg.data, data, sizeof(msg.data));
 800c480:   f107 030c   add.w   r3, r7, #12
 800c484:   330b        adds    r3, #11
 800c486:   2208        movs    r2, #8
 800c488:   6879        ldr r1, [r7, #4]
 800c48a:   4618        mov r0, r3
 800c48c:   f7f4 f82e   bl  80004ec <memcpy>
  msg.data = *data;                  // Hard Fault
 800c490:   687b        ldr r3, [r7, #4]
 800c492:   f107 0217   add.w   r2, r7, #23
 800c496:   cb03        ldmia   r3!, {r0, r1}
 800c498:   6010        str r0, [r2, #0]
 800c49a:   6051        str r1, [r2, #4]

我正在使用 GNU Arm Embedded Toolchain 5.4.1 20160919

这是一个最小的代码示例,它(希望)显示了问题所在。数据结构msg_t必须使用packed属性来匹配一些硬件寄存器。在微控制器上,此代码以 msg.data = *data;

行的硬故障结束
#include <stdint.h>
#include <string.h>
#include <stdio.h>

typedef struct canData_s {
  uint8_t d1;
  uint8_t d2;
  uint8_t d3;
  uint8_t d4;
  uint8_t d5;
  uint8_t d6;
  uint8_t d7;
  uint8_t d8; 
} canData_t;

#pragma pack(push, 1)
typedef struct msg_s {
  uint32_t stdId;
  uint32_t extId;
  uint8_t ide;
  uint8_t rtr;
  uint8_t dlc;
  canData_t data;  // 8 Bytes
  uint8_t navail;  // not available
  uint32_t timestamp;
} msg_t;
#pragma pack(pop)

void setData(canData_t *data) {
  msg_t msg;
  msg.data = *data;

  // Do something more ...
  printf("D1:%d", msg.data.d1);
  // ...
}

int main() {
  canData_t data;
  memset(&data, 0, 8);

  setData(&data);
}

为什么通过直接赋值复制结构会失败?

当您使用非标准 #pragma pack 时,您会强制编译器存储没有任何填充的结构。 data 之前的结构成员以 4+4+3 为一组,然后 data 在字节 11 处,这是未对齐的。

因此您强制 data 始终分配未对齐,如果将其作为字(32 位)访问,这可能会导致某些 CPU 出现硬件异常。编译器生成的代码 msg.data = *data; 可能假设当您复制两个结构时,它们总是正确对齐,通常情况就是如此。最有效的复制实现将使用 32 位数据块,所以这就是它将使用的内容。

这里的问题是为什么这个结构一开始就是打包的,因为它既不是硬件寄存器映射也不是数据协议映射。 CAN-bus IDE 和 RTR 之类的东西只是单个位;我非常怀疑任何 CAN 控制器都为此保留了一个完整的 8 位寄存器。例如,ST 的 "bxCAN" 控制器将这些作为单独的位放置在 CAN_TIxR 寄存器(CAN TX 邮箱标识符寄存器)中。市场上所有其他 CAN 控制器的行为都类似。

至于CAN帧本身,你不能直接对其进行内存映射。 CAN 控制器将抓取原始 CAN 帧并将其放入自己的内存映射寄存器中。

要么重新制作这个结构而不填充,要么使用硬件提供的实际 CAN 控制器寄存器。

我发现有一个 CFSR 寄存器,其中包含有关硬故障异常类型的信息。寄存器显示第 24 位已设置。 ARM 的编程手册 PM0214 在第 221 页说:

Bit 24 UNALIGNED: Unaligned access usage fault. Enable trapping of unaligned accesses by setting the UNALIGN_TRP bit in the CCR to 1, see Configuration and control register (CCR) on page 214. Unaligned LDM, STM, LDRD, and STRD instructions always fault irrespective of the setting of UNALIGN_TRP.

0: No unaligned access fault, or unaligned access trapping not enabled

1: The processor has made an unaligned memory access.

这确实符合@Lundin 的回答。