У меня есть сырые двоичные блоки данных (на самом деле, 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;
}
Есть ли какое-нибудь независимое от платформы решение, которое не повлияет на производительность?
предостережение — в этом ответе предполагается, что целочисленное представление машины является порядком байтов, как и вопрос.
только Платформо-независимый и правильный способ — использовать 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
Других решений пока нет …