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

Posted

技术标签:

【中文标题】如何从二进制数据中读取数字,跨平台(C/C++)?【英文标题】:How to read numeric from binary data, crossplatform (C/C++)? 【发布时间】:2016-06-12 15:42:09 【问题描述】:

我有原始二进制数据块(实际上是CBOR-encoded)。要读取数字,我使用常见的形式,例如:

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;

是否有任何不影响性能的独立于平台的解决方案?

【问题讨论】:

我认为这可能和你能得到的一样好。 使用正确的(反)序列化而不是这些未定义的行为重新解释。您已经遇到了一些问题,可能还有更多。 【参考方案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

【讨论】:

废话!唯一兼容且独立于平台的方法是使用位移进行序列化! memcpy 不关心字节序或表示。 Endianess 不是问题,因为我需要的所有编译器都具有转换字节顺序的内在特性。当没有内在的,我回退到位移。 @olaf 我知道字节序问题,并假设 OP 知道它。我正在解决将字节流传输为整数的问题。我会在答案中添加一个警告。 @Olaf 已更新,并添加了一个字节序证明解决方案,其好处是它在 little-endian x86 上仍然 100% 有效。 @SBKarr:首先使用正确的序列化有什么问题?一个好的编译器不会生成比重新解释的“hackish”方法更糟糕的代码,但是不需要测试,也不需要其他依赖于目标的东西。

以上是关于如何从二进制数据中读取数字,跨平台(C/C++)?的主要内容,如果未能解决你的问题,请参考以下文章

Dask 从二进制文件中读取数据

如何从二进制文件中读取块并使用 Python 或 Perl 解包提取结构?

在python中如何从二进制文件中读取信息

Pentaho Kettle - 从二进制类型的字段将十六进制转换为数字

尝试从二进制文件读取时引发违规

从二进制文件中读取矩阵