从阵列投射导致某些 MCU 崩溃,但不会导致其他 MCU 崩溃
Cast from array causing crash on some MCUs but not others
我有一段代码如下所示:
void update_clock(uint8_t *time_array)
{
time_t time = *((time_t *) &time_array[0]); // <-- hangs
/* ... more code ... */
}
其中 time_array
是一个 4 字节的数组(即 uint8_t time_array[4]
)。
我正在使用 arm-none-eabi-gcc 为 STM32L4 处理器编译它。
几个月前编译时,我没有遇到任何错误,代码在我所有的测试 MCU 上 运行 都非常好。回到这个项目时,我对我的环境 (OpenSTM32) 做了一些更新,现在这段代码在某些 MCU 上崩溃,而在其他 MCU 上运行良好。
我还有几个月前的二进制文件,并且已经确认此代码路径在我的所有 MCU 上都能正常工作(我有大约 5 个要测试),但现在它在其中两个上工作,同时导致其中三个坠毁。
我已经通过像这样重写代码来缓解问题:
time_t time = (
((uint32_t) time_array[0]) << 0 |
((uint32_t) time_array[1]) << 8 |
((uint32_t) time_array[2]) << 16 |
((uint32_t) time_array[3]) << 24
);
虽然这目前有效,但我认为旧代码看起来更清晰,而且我也担心如果此代码路径挂起,我可能会在其他地方出现类似的错误。
有谁知道是什么原因造成的?我可以更改我的设置中的任何内容以使编译器再次以旧方式工作吗?
您的问题是由于未对齐访问或写入错误区域引起的。
正在编译
#include "stdint.h"
#include "time.h"
time_t myTime;
void update_clock(uint8_t *time_array)
{
myTime = *((time_t *) &time_array[0]); // <-- hangs
/* ... more code ... */
}
使用带有参数 -march=armv7-m -Os
的 GCC 7.2.1 生成以下
update_clock(unsigned char*):
ldr r3, .L2
ldrd r0, [r0]
strd r0, [r3]
bx lr
.L2:
.word .LANCHOR0
myTime:
因为您的时间数组是 8 位类型,所以没有对齐规则,所以如果链接器没有对其进行字对齐,当您尝试将其取消引用为 time_t *
时 LDRD
指令被赋予非字对齐地址并导致使用错误。
LDRD
和 STRD
指令正在加载和存储 8 个字节,而您的数组只有 4 个字节长。我建议你在你的环境中检查sizeof(time_t)
,并制作一个足够长的对齐区域来存储它。
您的开发环境 OpenSTM32 可能正在使用 gcc 编译器。如果是这样,gcc 支持以下宏标志。
-fno-strict-aliasing
如果您正在使用 -O2
,这个标志可能会解决您的问题。
使用 memcpy
是标准建议,有时会被编译器优化掉:
memcpy(&time, time_array, sizeof time);
最后,您可以使用 gcc
的 typeof
和带有联合的复合文字来生成以下安全转换:
#define PUN_CAST4(a, x) ((union {uint8_t src[4]; typeof(x) dst;}){{a[0],a[1],a[2],a[3]}}).dst
time_t time = PUN_CAST4(time_array, time);
举个例子,下面的代码是在https://godbolt.org/g/eZRXxW编译的:
#include <stdint.h>
#include <time.h>
#include <string.h>
time_t update_clock(uint8_t *time_array) {
time_t t = *((time_t *) &time_array[0]); // assumes no alignment problem
return t;
}
time_t update_clock2(uint8_t *time_array) {
time_t t =
(uint32_t)time_array[0] << 0 |
(uint32_t)time_array[1] << 8 |
(uint32_t)time_array[2] << 16 |
(uint32_t)time_array[3] << 24;
return t;
}
time_t update_clock3(uint8_t *time_array) {
time_t t;
memcpy(&t, time_array, sizeof t);
return t;
}
#define PUN_CAST4(a, x) ((union {uint8_t src[4]; typeof(x) dst;}){{a[0],a[1],a[2],a[3]}}).dst
time_t update_clock4(uint8_t *time_array) {
time_t t = PUN_CAST4(time_array, t);
return t;
}
gcc 8.1 适用于所有四个示例:它使用 -O2
生成简单的代码。但是 gcc 7.3 对 4th 不好。 Clang 对于 32 位目标的 -m32
也适用于所有四个,但如果没有它
,则在第 2 和第 4 个失败
从版本 7-2017-q4-major 开始,arm gcc 附带了用 time_t
定义为 64 位 (long long
) 整数编译的 newlib,导致假设它的代码出现各种问题为 32 位。您的代码正在读取源数组的末尾,将存储在那里的任何内容作为时间值的 高阶位 ,可能导致大爆炸之前或之后的日期宇宙的热寂,这可能不是您的代码所期望的。
如果已知源数组包含32位数据,先将其复制到一个32位的int32_t
变量中,然后赋值给一个time_t
,这样就可以了正确转换,无论 time_t
.
的大小如何
我有一段代码如下所示:
void update_clock(uint8_t *time_array)
{
time_t time = *((time_t *) &time_array[0]); // <-- hangs
/* ... more code ... */
}
其中 time_array
是一个 4 字节的数组(即 uint8_t time_array[4]
)。
我正在使用 arm-none-eabi-gcc 为 STM32L4 处理器编译它。
几个月前编译时,我没有遇到任何错误,代码在我所有的测试 MCU 上 运行 都非常好。回到这个项目时,我对我的环境 (OpenSTM32) 做了一些更新,现在这段代码在某些 MCU 上崩溃,而在其他 MCU 上运行良好。
我还有几个月前的二进制文件,并且已经确认此代码路径在我的所有 MCU 上都能正常工作(我有大约 5 个要测试),但现在它在其中两个上工作,同时导致其中三个坠毁。
我已经通过像这样重写代码来缓解问题:
time_t time = (
((uint32_t) time_array[0]) << 0 |
((uint32_t) time_array[1]) << 8 |
((uint32_t) time_array[2]) << 16 |
((uint32_t) time_array[3]) << 24
);
虽然这目前有效,但我认为旧代码看起来更清晰,而且我也担心如果此代码路径挂起,我可能会在其他地方出现类似的错误。
有谁知道是什么原因造成的?我可以更改我的设置中的任何内容以使编译器再次以旧方式工作吗?
您的问题是由于未对齐访问或写入错误区域引起的。
正在编译
#include "stdint.h"
#include "time.h"
time_t myTime;
void update_clock(uint8_t *time_array)
{
myTime = *((time_t *) &time_array[0]); // <-- hangs
/* ... more code ... */
}
使用带有参数 -march=armv7-m -Os
的 GCC 7.2.1 生成以下
update_clock(unsigned char*):
ldr r3, .L2
ldrd r0, [r0]
strd r0, [r3]
bx lr
.L2:
.word .LANCHOR0
myTime:
因为您的时间数组是 8 位类型,所以没有对齐规则,所以如果链接器没有对其进行字对齐,当您尝试将其取消引用为 time_t *
时 LDRD
指令被赋予非字对齐地址并导致使用错误。
LDRD
和 STRD
指令正在加载和存储 8 个字节,而您的数组只有 4 个字节长。我建议你在你的环境中检查sizeof(time_t)
,并制作一个足够长的对齐区域来存储它。
您的开发环境 OpenSTM32 可能正在使用 gcc 编译器。如果是这样,gcc 支持以下宏标志。
-fno-strict-aliasing
如果您正在使用 -O2
,这个标志可能会解决您的问题。
使用 memcpy
是标准建议,有时会被编译器优化掉:
memcpy(&time, time_array, sizeof time);
最后,您可以使用 gcc
的 typeof
和带有联合的复合文字来生成以下安全转换:
#define PUN_CAST4(a, x) ((union {uint8_t src[4]; typeof(x) dst;}){{a[0],a[1],a[2],a[3]}}).dst
time_t time = PUN_CAST4(time_array, time);
举个例子,下面的代码是在https://godbolt.org/g/eZRXxW编译的:
#include <stdint.h>
#include <time.h>
#include <string.h>
time_t update_clock(uint8_t *time_array) {
time_t t = *((time_t *) &time_array[0]); // assumes no alignment problem
return t;
}
time_t update_clock2(uint8_t *time_array) {
time_t t =
(uint32_t)time_array[0] << 0 |
(uint32_t)time_array[1] << 8 |
(uint32_t)time_array[2] << 16 |
(uint32_t)time_array[3] << 24;
return t;
}
time_t update_clock3(uint8_t *time_array) {
time_t t;
memcpy(&t, time_array, sizeof t);
return t;
}
#define PUN_CAST4(a, x) ((union {uint8_t src[4]; typeof(x) dst;}){{a[0],a[1],a[2],a[3]}}).dst
time_t update_clock4(uint8_t *time_array) {
time_t t = PUN_CAST4(time_array, t);
return t;
}
gcc 8.1 适用于所有四个示例:它使用 -O2
生成简单的代码。但是 gcc 7.3 对 4th 不好。 Clang 对于 32 位目标的 -m32
也适用于所有四个,但如果没有它
从版本 7-2017-q4-major 开始,arm gcc 附带了用 time_t
定义为 64 位 (long long
) 整数编译的 newlib,导致假设它的代码出现各种问题为 32 位。您的代码正在读取源数组的末尾,将存储在那里的任何内容作为时间值的 高阶位 ,可能导致大爆炸之前或之后的日期宇宙的热寂,这可能不是您的代码所期望的。
如果已知源数组包含32位数据,先将其复制到一个32位的int32_t
变量中,然后赋值给一个time_t
,这样就可以了正确转换,无论 time_t
.