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

时间:2018-02-12 11:13:48

标签: 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

前三行工作得很好。最后一个以硬故障异常结束。具有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);
}

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

2 个答案:

答案 0 :(得分:9)

当您使用非标准data时,强制编译器存储结构而不进行任何填充。 data之前的结构成员是4 + 4 + 3的组,然后是字节11的data,它是未对齐的。

因此,强制msg.data = *data;总是被分配未对齐,如果以字(32位)访问,可能会导致某些CPU出现硬件异常。编译器生成的代码order by to_number(regexp_substr(location_id, '\d+$')) 可能会假设当您复制两个结构时,它们总是正确对齐,通常就是这种情况。并且最有效的副本实现将适用于32位数据块,因此它将使用它。

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

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

重新制作此结构而不填充或使用硬件提供的实际CAN控制器寄存器。

答案 1 :(得分:0)

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

  

Bit 24 UNALIGNED:未对齐的访问使用故障。启用陷印   通过将CCR中的UNALIGN_TRP位设置为1来进行未对齐访问,请参阅   第214页的配置和控制寄存器(CCR)。未对齐的LDM,   无论是什么,STM,LDRD和STRD指令总是出错   设置UNALIGN_TRP。

     

0:没有未对齐的访问错误或未对齐   访问陷阱未启用

     

1:处理器未对齐   记忆访问。

这真的与@Lundin的答案相符。