[英]How do I read a little-endian 64-bit value from a byte buffer?
在 C 应用程序(不是 C++)中,我有一个通过 .network 接收的数据字节数组。 该数组长 9 个字节。 字节 1 到 8(从零开始)表示 64 位 integer 值作为小端。 我的 CPU 也使用小端。
如何将这些字节从数组转换为 integer 数字?
我试过这个:
uint8_t rx_buffer[2000];
//recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, ...)
int64_t sender_time_us = *(rx_buffer + 1);
但它给了我 89、219、234 或 27 之类的值。发件人将这些值视为 1647719702937548、1647719733002117 或 1647719743790424。(这些示例不匹配,它们只是随机样本。)
不安全的解决方案:
int64_t sender_time_us = *(int64_t*)(rx_buffer + 1);
这可能是 alignment 违规,并且是严格的别名规则违规。 这是未定义的行为。 在某些机器上,这可能会因总线错误而终止您的程序。
安全解决方案:
int64_t sender_time_us;
memcpy( &sender_time_us, rx_buffer + 1, sizeof( int64_t ) );
@Nate Eldredge 指出虽然这个解决方案可能看起来效率低下,但一个体面的编译器应该将其优化为高效的东西。 .net 效果将是 (a) 强制编译器正确处理未对齐的访问,如果目标需要任何特殊处理,(b) 让编译器正确理解别名并防止任何会破坏它的优化。 对于能够正常处理未对齐访问的目标,生成的代码可能根本不会改变。
您的代码仅获得一个uint8_t
。 您需要先转换为int64_t
。 是这样的:
int64_t* pBuffer = (int64_t*)(rx_buffer + 1);
int64_t sender_time_us = *pBuffer;
但是您应该知道,某些 CPU 可能不喜欢访问未对齐的 64 位值。 如果您知道字节顺序,这也可能没问题,但实际上以更便携的方式处理它会更好。
读取 little-endian 64 位值的可移植方法非常简单:
inline static uint64_t load_u64le(const void *p) {
const unsigned char *q = p;
uint64_t result = 0;
result |= q[7]; result <<= 8;
result |= q[6]; result <<= 8;
result |= q[5]; result <<= 8;
result |= q[4]; result <<= 8;
result |= q[3]; result <<= 8;
result |= q[2]; result <<= 8;
result |= q[1]; result <<= 8;
result |= q[0];
return result;
}
inline static int64_t load_i64le(const void *p) {
return (int64_t)load_u64le(p);
}
只需将此助手 function 调用为read_i64le(rx_buffer + 1)
。 现代编译器能够在可能的情况下将其优化为架构上的单个指令。
要读取一个 64 位值,其中您明确知道字节顺序与本机 ABI 一致,您可以使用以下命令:
inline static uint64_t load_u64(const void *p) {
uint64_t result;
memcpy(&result, p, sizeof(result));
return result;
}
它有更好的机会被优化为一个简单的加载,假设只假设编译器将一个短的memcpy
优化为一个内联 memory 加载。
为了获得最佳效果,您可以使用:
inline static uint64_t load_u64le(const void *p) {
uint64_t result = 0;
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
memcpy(&result, p, sizeof(result));
#else
const unsigned char *q = p;
result |= q[7]; result <<= 8;
result |= q[6]; result <<= 8;
result |= q[5]; result <<= 8;
result |= q[4]; result <<= 8;
result |= q[3]; result <<= 8;
result |= q[2]; result <<= 8;
result |= q[1]; result <<= 8;
result |= q[0];
#endif
return result;
}
现在,为什么你不应该像其他答案建议的那样转换偏移指针:首先,因为取消引用未对齐的指针是 UB。 并非每个体系结构都支持从任意地址读取大于 8 位的字,即使在那些支持它们的体系结构上,编译器仍可能假设所有取消引用的地址在生成代码时都正确对齐,尤其是在优化下。 如果您曾经使用 UBSan 运行您的代码,它也会报错。
第二个原因是严格的别名。 C 语言规定所有 memory 必须通过指向字符类型( char
、 signed char
或unsigned char
)的指针或指向 object 存储在 memory 中的类型的指针访问; 这确保可以假定指向不同类型的指针不会别名(指向同一内存)。 实际上, uint8_t
通常是unsigned char
的别名,它是字符类型,例外地允许别名任何类型; 到目前为止,这使得严格的别名问题主要是理论上的。 然而,也没有理由冒这个风险,因为避免它是如此容易和便宜。
你需要投射你的指针,像这样:
int64_t sender_time_us = *(int64_t*)(rx_buffer + 1);
实际上,您只会获得一个字节的数据。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.