从阵列投射导致某些 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指令被赋予非字对齐地址并导致使用错误。

LDRDSTRD 指令正在加载和存储 8 个字节,而您的数组只有 4 个字节长。我建议你在你的环境中检查sizeof(time_t),并制作一个足够长的对齐区域来存储它。

您的开发环境 OpenSTM32 可能正在使用 gcc 编译器。如果是这样,gcc 支持以下宏标志。

-fno-strict-aliasing

如果您正在使用 -O2,这个标志可能会解决您的问题。

使用 memcpy 是标准建议,有时会被编译器优化掉:

memcpy(&time, time_array, sizeof time);

最后,您可以使用 gcctypeof 和带有联合的复合文字来生成以下安全转换:

#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.

的大小如何