簡體   English   中英

雙打時在ARMv6上出現“總線錯誤”

[英]'Bus Error' on ARMv6 when working with doubles

我正在為ARMv6創建一個C ++程序,該程序由於總線錯誤而崩潰。 使用GDB,我已將問題追溯到以下代碼

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

該程序檢查收到的消息,並必須使用上述代碼提取一些雙精度值。 接收到的消息有多個字段,有些則沒有。

在x86架構上,此方法工作正常,但在ARM上,出現“總線錯誤”。 因此,我懷疑我的問題是數據的對齊方式-雙字段必須與ARM體系結構中內存中的字邊界對齊。

我已經嘗試了以下解決方案,但仍無法解決(仍然出現錯誤):

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

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

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

使用“ memcpy”是最好的方法嗎? 或者,還有更好的方法?

就我而言,我無法控制緩沖區中數據的打包或消息中字段的順序。

相關問題: Armv7(RPi2)上的std :: atomic <double>和對齊/總線錯誤

使用“ memcpy”是最好的方法嗎?

通常,這是唯一正確的方法,除非您針對的是單個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

我可以保證,即使在一台可以“運行”的機器上,也不會有少於1條指令正確地加載未定義行為的黑客程序。 請注意,NEON是此處的關鍵角色vld1僅要求將基地址與元素大小對齊,因此對於8位元素,它永遠不能對齊。 在更一般的情況下(例如,如果是long long而不是double ),您可能仍需要-munaligned-access來像以前一樣說服編譯器。

為了進行比較,讓我們看一下每個人最喜歡的1970s突變子孫代計算器芯片的票價:

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

是的,正確的代碼仍然看起來像最好的代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM