为什么通过直接分配复制结构会失败?

eDe*_*ser 10 c struct stm32 memcpy

我在将微控制器上的一些数据从一个结构复制到另一个结构时遇到了硬错误异常.我尝试了不同的实现,它们应该完全相同.看我的代码行:

memcpy(&msg.data, data, 8);
memcpy(&msg.data, data, sizeof(*data));
memcpy(&msg.data, data, sizeof(msg.data));
msg.data = *data;  // Hard Fault
Run Code Online (Sandbox Code Playgroud)

前三行工作得很好.最后一个以硬故障异常结束.线的组装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]
Run Code Online (Sandbox Code Playgroud)

我正在使用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);
}
Run Code Online (Sandbox Code Playgroud)

为什么通过直接分配复制结构会失败?

Lun*_*din 9

当您使用非标准时#pragma pack,强制编译器存储结构而不进行任何填充.之前的struct成员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控制器寄存器.