2016-06-12 3 views
1

У меня есть необработанные двоичные блоки данных (на самом деле, CBOR -encoded). Для чтения числового я использую общий вид, как:Как читать числовые данные из двоичных данных, кроссплатформенность (C/C++)?

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

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

+0

Я думаю, что это, вероятно, так же хорошо, как вы получите. –

+0

Используйте правильную сериализацию (de) вместо этих неопределенных переопределений поведения. Вы столкнулись с некоторыми проблемами, может быть и больше. – Olaf

ответ

4

caveat - этот ответ предполагает, что целочисленное представление машины малопригодно, равно как и вопрос.

только Независимо от платформы и правильный способ использования 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 
+0

Глупости! Единственный совместимый и независимый от платформы способ - это сериализация с помощью бит-сдвигов! 'memcpy' не заботится об энтерианстве или представлении. – Olaf

+0

Endianess не проблема, так как для всех компиляторов мне необходимо встроить порядок байтов. Когда нет внутреннего, я возвращаюсь к бит-сдвигам. – SBKarr

+0

@olaf Я знаю проблему с энтианностью и предполагаю, что OP знает об этом. Я обращался к вопросу о передаче байтового потока в целое число. Я добавлю оговорку к ответу. –

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