'总线错误'使用双精度程序

时间:2016-01-28 20:44:51

标签: c++ arm

我正在为ARMv6创建一个与BUS ERROR崩溃的C ++程序。使用GDB我已将问题追溯到以下代码

double d = *(double*)pData; pData += sizeof(int64_t);  // char *pData

程序经过收到的消息,必须使用上面的代码提取一些双值。收到的消息有几个字段,有些则不是双倍的字段。

在x86架构上,这样可以正常工作,但在ARM上我遇到了“总线错误”#39;因此,我怀疑我的问题是数据对齐 - 双字段必须与ARM体系结构内存中的字边界对齐。

我已尝试以下作为修复,但无效(仍然出现错误):

int64_t i = *(int64_t*)pData;
double d = *((double*)&i);

以下工作(到目前为止):

double d = 0;
memcpy(&d, pData, sizeof(double));

使用' memcpy'最好的方法?或者,还有更好的方法?

就我而言,我无法控制缓冲区中数据的打包或消息中字段的顺序。

相关问题:std::atomic<double> on Armv7 (RPi2) and alignment/bus errors

1 个答案:

答案 0 :(得分:2)

  

使用&#39; memcpy&#39;最好的方法?

一般来说,它只是 正确的方法,除非您针对单个ABI,其中任何类型都不需要超过1个字节的对齐。

C ++标准相当冗长,所以我引用C标准更简洁地表达同样的事情:

  

指向对象或不完整类型的指针可能会转换为指向不同对象或不完整类型的指针。如果结果指针未针对指向类型正确对齐,则行为未定义。

它是:永远存在未定义行为的幽灵。即使是x86编译器也完全可以闯进你的房子,在你睡觉的时候把卡子塞进你的头发,而不是按照你期望的方式加载那些数据,如果ABI这么说的话。

有一点需要注意的是,现代编译器往往足够聪明,正确性并不一定以性能为代价。让我们充实示例代码:

#include <string.h>

double func(char *data) {
    double d;
    memcpy(&d, data, sizeof d);
    return d;
}

...并将其抛给编译器:

$ clang -target arm -march=armv6 -mfpu=vfpv3 -mfloat-abi=hard -O1 -S test.c
...
func:                                   @ @func
        .fnstart
@ BB#0:
        push    {r4, r5, r11, lr}
        sub     sp, sp, #8
        mov     r2, r0
        ldrb    r1, [r0, #3]
        ldrb    r3, [r0, #2]
        ldrb    r12, [r0]
        ldrb    lr, [r0, #1]
        ldrb    r4, [r2, #4]!
        orr     r5, r3, r1, lsl #8
        ldrb    r3, [r2, #2]
        ldrb    r2, [r2, #3]
        ldrb    r0, [r0, #5]
        orr     r1, r12, lr, lsl #8
        orr     r2, r3, r2, lsl #8
        orr     r0, r4, r0, lsl #8
        orr     r1, r1, r5, lsl #16
        orr     r0, r0, r2, lsl #16
        str     r1, [sp]
        str     r0, [sp, #4]
        vpop    {d0}
        pop     {r4, r5, r11, pc}

好的,所以用字节memcpy来安全地玩游戏;至少它是内联的。但是,如果CPU配置正确的话,ARMv6至少支持未对齐的字和半字访问 - 让我们告诉编译器我们很酷:

$ clang -target arm -march=armv6 -mfpu=vfpv3 -mfloat-abi=hard -O1 -S -munaligned-access test.c
...
func:                                   @ @func
        .fnstart
@ BB#0:
        sub     sp, sp, #8
        ldr     r1, [r0]
        ldr     r0, [r0, #4]
        str     r0, [sp, #4]
        str     r1, [sp]
        vpop    {d0}
        bx      lr

我们走了,关于只用整数字加载可以做的最好的事情。现在,如果我们为更新的东西编译它会怎么样?

$ clang -target arm -march=armv7 -mfpu=neon-vfpv4 -mfloat-abi=hard -O1 -S test.c
...
func:                                   @ @func
        .fnstart
@ BB#0:
        vld1.8  {d0}, [r0]
        bx      lr

我可以保证,即使在它可以工作的机器上,没有未定义的行为 - hackery会在少于一条指令中正确加载那个未对齐的double。请注意,NEON是这里的关键参与者 - vld1只需要将基地址与元素大小对齐,因此对于8位元素,它永远不会是未对齐的。在更一般的情况下(例如,如果它是long long而不是double),您可能仍需要-munaligned-access来像以前一样说服编译器。

为了进行比较,让我们看看每个人最喜欢的20世纪70年代的计算机芯片的变种孙子:&/ p>

clang -O1 -S test.c
...
func:                                   # @func
# BB#0:
        movl    4(%esp), %eax
        fldl    (%eax)
        retl

是的,正确的代码看起来仍然是最好的代码。