如何从二进制数据中读取数字,跨平台(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++)?的主要内容,如果未能解决你的问题,请参考以下文章
如何从二进制文件中读取块并使用 Python 或 Perl 解包提取结构?