如何从二进制数据中读取数字,跨平台(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
我有原始二进制数据块(实际上,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