2016-01-28 2 views
0

Я создаю программу на C++ для ARMv6, которая вылетает с BUS ERROR. Использование GDB я проследил проблему следующего кода«Ошибка шины» на ARMv6 при работе с удвоениями

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)); 

Пользуется «тетсру «лучший подход? Или, есть ли лучший способ?

В моем случае у меня нет контроля над упаковкой данных в буфере или порядком полей в сообщении.

Связанные вопрос: std::atomic<double> on Armv7 (RPi2) and alignment/bus errors

+1

Вы сами ответили на свой вопрос. Чтобы получить доступ к двойным данным, вы должны присутствовать на выровненном адресе памяти, и никакие трюки с приложением не помогут - вам действительно нужно ** поместить ** данные по выровненному адресу. – SergeyA

+0

Если вы можете управлять буфером, вам может понадобиться нанести недвойные данные, чтобы удвоить всегда правильно выровнены. Чем больше вы используете буфер, тем лучше можно копировать. – SergeyA

+0

@SergeyA thx, я получаю данные по потоку TCP, и я пытаюсь разобрать данные inline –

ответ

2

Пользуется 'тетсру' лучший подход?

В целом это только правильный подход, если вы не ориентируетесь на одного 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; по крайней мере, он вложен.Но эй, 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 

Я могу гарантировать, что, даже на машине, где он будет «работать», не неопределенными-поведение-повозка, запряженная волами будет уложен правильно, что выровненным двойной менее чем одной инструкции. Обратите внимание, что 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 

Да, правильный код еще и выглядит как лучший код.

+0

Почему вы не использовали опцию -munaligned-access в деле armv7? –

+0

Пояснение добавлено. Как бы то ни было, я привык к сборкам GCC, которые я обычно использую, если для целей v7 по умолчанию включен unaligned доступ. При дальнейшем исследовании (с разными типами) этот Clang (3.7.1, Arch Linux i686) не работает, но я не заметил, потому что он не влияет на пример, который я хотел сделать;) – Notlikethat

Смежные вопросы