我正在开发 memcache 协议的实现,该协议在某些时候使用 64 位整数值。这些值必须以“网络字节顺序”存储。
我希望有一些
uint64_t htonll(uint64_t value)
功能可以进行更改,但不幸的是,如果它存在,我找不到它。
所以我有 1 或 2 个问题:
我想到了一个基本的实现,但我不知道如何在编译时检查字节顺序以使代码可移植。所以我们非常欢迎您的帮助;)
谢谢你。
这是我写的最终解决方案,感谢布莱恩的解决方案。
uint64_t htonll(uint64_t value)
{
// The answer is 42
static const int num = 42;
// Check the endianness
if (*reinterpret_cast<const char*>(&num) == num)
{
const uint32_t high_part = htonl(static_cast<uint32_t>(value >> 32));
const uint32_t low_part = htonl(static_cast<uint32_t>(value & 0xFFFFFFFFLL));
return (static_cast<uint64_t>(low_part) << 32) | high_part;
} else
{
return value;
}
}
#define htonll(x) ((1==htonl(1)) ? (x) : ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
#define ntohll(x) ((1==ntohl(1)) ? (x) : ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
测试 (1==htonl(1)) 只是确定(遗憾的是在运行时)硬件架构是否需要字节交换。没有任何可移植的方法可以在编译时确定架构是什么,因此我们求助于使用“htonl”,它在这种情况下是可移植的。如果需要字节交换,那么我们使用 htonl 一次交换 32 位(记住也要交换两个 32 位字)。
这是执行交换的另一种方法,它可以跨大多数编译器和操作系统移植,包括 AIX、BSD、Linux 和 Solaris。
#if __BIG_ENDIAN__
# define htonll(x) (x)
# define ntohll(x) (x)
#else
# define htonll(x) (((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
# define ntohll(x) (((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
#endif
重要的部分是使用
__BIG_ENDIAN__
或 __LITTLE_ENDIAN__
;而不是 __BYTE_ORDER__
、__ORDER_BIG_ENDIAN__
或 __ORDER_LITTLE_ENDIAN__
。有些编译器和操作系统缺少 __BYTE_ORDER__
和朋友。
您可能正在寻找
bswap_64
我认为它几乎到处都受支持,但我不会称其为标准。
您可以通过创建值为 1 的 int、将 int 的地址转换为
char*
并检查第一个字节的值来轻松检查字节顺序。
例如:
int num = 42;
if(*(char *)&num == 42)
{
//Little Endian
}
else
{
//Big Endian
}
了解了这一点,您还可以创建一个简单的函数来进行交换。
您也可以始终使用包含可移植跨平台的字节序宏的 boost。
您可以尝试使用
uint64_t htobe64(uint64_t host_64bits)
&
uint64_t be64toh(uint64_t big_endian_64bits)
反之亦然。
这似乎在 C 中可行;我做错了什么吗?
uint64_t htonll(uint64_t value) {
int num = 42;
if (*(char *)&num == 42) {
uint32_t high_part = htonl((uint32_t)(value >> 32));
uint32_t low_part = htonl((uint32_t)(value & 0xFFFFFFFFLL));
return (((uint64_t)low_part) << 32) | high_part;
} else {
return value;
}
}
减少“if num == ...”的开销 使用预处理器定义:
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#else
#endif
嗯,我认为最好在编译时尽可能使用字节序切换,但我更喜欢使用函数而不是宏,因为在宏中参数只是被参数替换;因此,如果参数在宏中多次出现(如前面提供的一些解决方案中所做的那样),则可以对参数进行多次求值并创建奇怪的结果。
uint64_t htonll(uint64_t x)
{
#if __BIG_ENDIAN__
return x;
#else
return ((uint64_t)htonl((x) & 0xFFFFFFFFLL) << 32) | htonl((x) >> 32);
#endif
}
uint64_t ntohll(uint64_t x)
{
#if __BIG_ENDIAN__
return x;
#else
return ((uint64_t)ntohl((x) & 0xFFFFFFFFLL) << 32) | ntohl((x) >> 32);
#endif
}
因此,这允许调用 htonll(x++) 而无需多次递增 x,就像使用之前的宏一样。
我建议使用endian.h
中定义的函数由于网络字节顺序按照惯例是大端字节序,因此您只需从主机调用适当的函数即可“be”。它包括 16、32 和 64 位版本。
我创建了以下单个 ntoh 模板化函数,适用于所有整数类型,因此我不必担心要调用哪个函数。
template <typename T> std::enable_if_t<sizeof(T) == 2, T> ntoh(T v) { return be16toh(v); }
template <typename T> std::enable_if_t<sizeof(T) == 4, T> ntoh(T v) { return be32toh(v); }
template <typename T> std::enable_if_t<sizeof(T) == 8, T> ntoh(T v) { return be64toh(v); }
template <typename T> std::enable_if_t<sizeof(T) == 2, T> hton(T v) { return htobe16(v); }
template <typename T> std::enable_if_t<sizeof(T) == 4, T> hton(T v) { return htobe32(v); }
template <typename T> std::enable_if_t<sizeof(T) == 8, T> hton(T v) { return htobe64(v); }
在 Linux 上,对该函数的调用没有任何损失,因为不需要进行任何转换。 在 Windows 上,谁关心 Windows ;-P
希望它对某人有帮助。
编辑:将两者结合起来(使用布莱恩的代码):
uint64_t htonll(uint64_t value)
{
int num = 42;
if(*(char *)&num == 42)
return (htonl(value & 0xFFFFFFFF) << 32LL) | htonl(value >> 32);
else
return value;
}
警告:未经测试的代码!使用前请先测试。