将微 Controller 上的某些数据从一个结构复制到另一个结构时,我遇到了“硬故障”异常。我尝试了应该完全相同的不同实现。看我的代码行:

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

前三行工作正常。最后一个以Hard Fault Exception结尾。带有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属性来匹配某些硬件寄存器。在微 Controller 上,此代码在hardt错误处以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之前的struct成员是4 + 4 + 3的组,然后是字节11处的data,这是未对齐的。

因此,您必须强制data始终分配为未对齐状态,如果将其作为一个字(32位)进行访问,则可能会导致某些CPU出现硬件异常。编译器生成的代码msg.data = *data;可能假定复制两个结构时,它们始终正确对齐,通常是这种情况。副本的最有效实现将处理32位数据块,因此将使用该副本。

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

至于CAN框架本身,您不能直接对其进行内存映射。 CAN Controller 将抓取原始CAN帧并将其放置在其自己的内存映射寄存器中。

无需填充即可重新构建此结构,或使用硬件提供的实际CAN Controller 寄存器。

关于c - 为什么通过直接分配复制结构失败?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48745111/

10-11 17:47
查看更多