如何从二进制数据中读取数字,跨平台(C/C++)?

How to read numeric from binary data, crossplatform (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 PC 和 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 是一个神奇的函数,编译器会 'do the right thing'.

为 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    , %rsp
        call    get_bytes()
        movq    (%rax), %rdi         ; note - memcpy utterly elided
        call    emit(unsigned long)
        xorl    %eax, %eax
        addq    , %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    , %rsp
        call    get_bytes()
        movq    (%rax), %rdi
        call    emit(unsigned long)
        xorl    %eax, %eax
        addq    , %rsp
        ret