Android — Как читать числовые из двоичных данных, кроссплатформенность (C / C ++)?

У меня есть сырые двоичные блоки данных (на самом деле, CBORзакодирована). Для чтения чисел я использую общую форму, такую ​​как:

template <typename T> // T can be uint64_t, double, uint32_t, etc...
auto read(const uint8_t *ptr) -> T {
return *((T *)(ptr)); // all endianess-aware functions will be performed later
}

Это решение работает на x86/x86_64 ПК и arm/arm64 IOS.
Но на arm/armv7 Android с clang компилятор на уровне оптимизации выпуска по умолчанию (-Os) я получил SIGBUS с кодом 1 (чтение без выравнивания) для типов, размер которых превышает один байт. Я исправляю эту проблему с помощью другого решения:

template <typename T>
auto read(const uint8_t *ptr) -> T {
union {
uint8_t buf[sizeof(T)];
T value;
} u;
memcpy(u.buf, ptr, sizeof(T));
return u.value;
}

Есть ли какое-нибудь независимое от платформы решение, которое не повлияет на производительность?

1

Решение

предостережение — в этом ответе предполагается, что целочисленное представление машины является порядком байтов, как и вопрос.

только Платформо-независимый и правильный способ — использовать memcpy. Вам не нужен союз.

Не беспокойтесь об эффективности. memcpy — волшебная функция, и компилятор «сделает все правильно».

пример при компиляции для x86:

#include <cstring>
#include <cstdint>

template <typename T>
auto read(const uint8_t *ptr) -> T {
T result;
std::memcpy(&result, ptr, sizeof(T));
return result;
}

extern const uint8_t* get_bytes();
extern void emit(std::uint64_t);

int main()
{
auto x = read<std::uint64_t>(get_bytes());
emit(x);

}

Выходит ассемблер:

main:
subq    $8, %rsp
call    get_bytes()
movq    (%rax), %rdi         ; note - memcpy utterly elided
call    emit(unsigned long)
xorl    %eax, %eax
addq    $8, %rsp
ret

примечание: порядковый номер

Вы можете сделать это решение по-настоящему переносимым, добавив проверку порядка выполнения во время выполнения. На самом деле, проверка будет исключена, поскольку компилятор увидит это:

constexpr bool is_little_endian()
{
short int number = 0x1;
char *numPtr = (char*)&number;
return (numPtr[0] == 1);
}template <typename T>
auto read(const uint8_t *ptr) -> T {
T result = 0;
if (is_little_endian())
{
std::memcpy(&result, ptr, sizeof(result));
}
else
{
for (T i = 0 ; i < sizeof(T) ; ++i)
{
result += *ptr++ << 8*i;
}
}
return result;
}

Полученный машинный код остается неизменным:

main:
subq    $8, %rsp
call    get_bytes()
movq    (%rax), %rdi
call    emit(unsigned long)
xorl    %eax, %eax
addq    $8, %rsp
ret
4

Другие решения

Других решений пока нет …