为什么通过直接赋值复制结构会失败?
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 的回答。
我 运行 在将微控制器上的一些数据从一个结构复制到另一个结构时遇到硬故障异常。我尝试了不同的实现,它们应该做的都是一样的。查看我的代码行:
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 的回答。