C++ 中的 64 位 ntohl()?
- 2024-10-14 08:40:00
- admin 原创
- 80
问题描述:
的手册页htonl()
似乎建议您只能将其用于最多 32 位值。(实际上,ntohl()
它被定义为无符号长整型,在我的平台上是 32 位。我想如果无符号长整型是 8 个字节,它将适用于 64 位整数)。
我的问题是我需要将 64 位整数(在我的情况下,这是一个无符号长整型)从大端转换为小端。现在,我需要进行特定的转换。但如果ntohl()
目标平台是大端,函数(如)不会转换我的 64 位值,那就更好了。(我宁愿避免添加自己的预处理器魔法来做到这一点)。
我可以使用什么?如果存在的话,我希望它是标准的,但我愿意听取实施建议。我过去曾见过使用联合进行这种类型的转换。我想我可以有一个 unsigned long long 和 char[8] 的联合。然后相应地交换字节。(显然会在大端平台上中断)。
解决方案 1:
文档:man htobe64
在 Linux(glibc >= 2.9)或 FreeBSD 上。
不幸的是,在 2009 年的尝试中,OpenBSD、FreeBSD 和 glibc(Linux)未能顺利地合作为此创建一个(非内核 API)libc 标准。
目前,这是一段简短的预处理器代码:
#if defined(__linux__)
# include <endian.h>
#elif defined(__FreeBSD__) || defined(__NetBSD__)
# include <sys/endian.h>
#elif defined(__OpenBSD__)
# include <sys/types.h>
# define be16toh(x) betoh16(x)
# define be32toh(x) betoh32(x)
# define be64toh(x) betoh64(x)
#endif
(在 Linux 和 OpenBSD 上测试)应该会隐藏差异。它为您提供了这 4 个平台上的 Linux/FreeBSD 样式的宏。
使用示例:
#include <stdint.h> // For 'uint64_t'
uint64_t host_int = 123;
uint64_t big_endian;
big_endian = htobe64( host_int );
host_int = be64toh( big_endian );
这是目前最“标准 C 库”式的方法。
解决方案 2:
我建议你读一下这个: http: //commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
uint64_t
ntoh64(const uint64_t *input)
{
uint64_t rval;
uint8_t *data = (uint8_t *)&rval;
data[0] = *input >> 56;
data[1] = *input >> 48;
data[2] = *input >> 40;
data[3] = *input >> 32;
data[4] = *input >> 24;
data[5] = *input >> 16;
data[6] = *input >> 8;
data[7] = *input >> 0;
return rval;
}
uint64_t
hton64(const uint64_t *input)
{
return (ntoh64(input));
}
int
main(void)
{
uint64_t ull;
ull = 1;
printf("%"PRIu64"
", ull);
ull = ntoh64(&ull);
printf("%"PRIu64"
", ull);
ull = hton64(&ull);
printf("%"PRIu64"
", ull);
return 0;
}
将显示以下输出:
1
72057594037927936
1
如果删除高 4 个字节,则可以用 ntohl() 来测试这一点。
您还可以将其转变为 C++ 中一个很好的模板函数,它可以对任何大小的整数起作用:
template <typename T>
static inline T
hton_any(const T &input)
{
T output(0);
const std::size_t size = sizeof(input);
uint8_t *data = reinterpret_cast<uint8_t *>(&output);
for (std::size_t i = 0; i < size; i++) {
data[i] = input >> ((size - i - 1) * 8);
}
return output;
}
现在您的 128 位也是安全的!
解决方案 3:
快速回答
#include <endian.h> // __BYTE_ORDER __LITTLE_ENDIAN
#include <byteswap.h> // bswap_64()
uint64_t value = 0x1122334455667788;
#if __BYTE_ORDER == __LITTLE_ENDIAN
value = bswap_64(value); // Compiler builtin GCC/Clang
#endif
头文件
正如zhaorufei所报告的(参见她/他的评论)endian.h
,不是 C++ 标准头文件,并且宏__BYTE_ORDER
和__LITTLE_ENDIAN
可能未定义。因此,该#if
语句不可预测,因为未定义的宏被视为0
。
如果您想分享检测字节顺序的 C++ 优雅技巧,请编辑此答案。
可移植性
此外,该宏bswap_64()
适用于 GCC 和 Clang 编译器,但不适用于 Visual C++ 编译器。为了提供可移植的源代码,您可能会受到以下代码片段的启发:
#ifdef _MSC_VER
#include <stdlib.h>
#define bswap_16(x) _byteswap_ushort(x)
#define bswap_32(x) _byteswap_ulong(x)
#define bswap_64(x) _byteswap_uint64(x)
#else
#include <byteswap.h> // bswap_16 bswap_32 bswap_64
#endif
另请参阅更便携的源代码:跨平台_byteswap_uint64
C++14constexpr
模板函数
通用hton()
16 位、32 位、64 位及更多...
#include <endian.h> // __BYTE_ORDER __LITTLE_ENDIAN
#include <algorithm> // std::reverse()
template <typename T>
constexpr T htonT (T value) noexcept
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
char* ptr = reinterpret_cast<char*>(&value);
std::reverse(ptr, ptr + sizeof(T));
#endif
return value;
}
C++11constexpr
模板函数
C++11 不允许
constexpr
函数中有局部变量。
因此,诀窍是使用具有默认值的参数。
此外,C++11
constexpr
函数必须包含一个表达式。因此,函数体由一个 return 和一些逗号分隔的语句
组成。
template <typename T>
constexpr T htonT (T value, char* ptr=0) noexcept
{
return
#if __BYTE_ORDER == __LITTLE_ENDIAN
ptr = reinterpret_cast<char*>(&value),
std::reverse(ptr, ptr + sizeof(T)),
#endif
value;
}
在 clang-3.5 和 GCC-4.9 上使用时均无编译警告(参见coliru-Wall -Wextra -pedantic
上的编译和运行输出)。
C++11constexpr
模板 SFINAE 函数
但是上述版本不允许创建constexpr
变量,如下所示:
constexpr int32_t hton_six = htonT( int32_t(6) );
最后,我们需要根据 16/32/64 位分离(专门化)函数。
但我们仍然可以保留通用函数。 (请参阅coliru
上的完整代码片段)
下面的 C++11 代码片段使用特征 std::enable_if
来利用替换失败不是错误(SFINAE)。
template <typename T>
constexpr typename std::enable_if<sizeof(T) == 2, T>::type
htonT (T value) noexcept
{
return ((value & 0x00FF) << 8)
| ((value & 0xFF00) >> 8);
}
template <typename T>
constexpr typename std::enable_if<sizeof(T) == 4, T>::type
htonT (T value) noexcept
{
return ((value & 0x000000FF) << 24)
| ((value & 0x0000FF00) << 8)
| ((value & 0x00FF0000) >> 8)
| ((value & 0xFF000000) >> 24);
}
template <typename T>
constexpr typename std::enable_if<sizeof(T) == 8, T>::type
htonT (T value) noexcept
{
return ((value & 0xFF00000000000000ull) >> 56)
| ((value & 0x00FF000000000000ull) >> 40)
| ((value & 0x0000FF0000000000ull) >> 24)
| ((value & 0x000000FF00000000ull) >> 8)
| ((value & 0x00000000FF000000ull) << 8)
| ((value & 0x0000000000FF0000ull) << 24)
| ((value & 0x000000000000FF00ull) << 40)
| ((value & 0x00000000000000FFull) << 56);
}
或者基于内置编译器宏和 C++14 语法的更短版本std::enable_if_t<xxx>
作为快捷方式std::enable_if<xxx>::type
:
template <typename T>
constexpr typename std::enable_if_t<sizeof(T) == 2, T>
htonT (T value) noexcept
{
return bswap_16(value); // __bswap_constant_16
}
template <typename T>
constexpr typename std::enable_if_t<sizeof(T) == 4, T>
htonT (T value) noexcept
{
return bswap_32(value); // __bswap_constant_32
}
template <typename T>
constexpr typename std::enable_if_t<sizeof(T) == 8, T>
htonT (T value) noexcept
{
return bswap_64(value); // __bswap_constant_64
}
第一版测试代码
std::uint8_t uc = 'B'; std::cout <<std::setw(16)<< uc <<'
';
uc = htonT( uc ); std::cout <<std::setw(16)<< uc <<'
';
std::uint16_t us = 0x1122; std::cout <<std::setw(16)<< us <<'
';
us = htonT( us ); std::cout <<std::setw(16)<< us <<'
';
std::uint32_t ul = 0x11223344; std::cout <<std::setw(16)<< ul <<'
';
ul = htonT( ul ); std::cout <<std::setw(16)<< ul <<'
';
std::uint64_t uL = 0x1122334455667788; std::cout <<std::setw(16)<< uL <<'
';
uL = htonT( uL ); std::cout <<std::setw(16)<< uL <<'
';
第二版测试代码
constexpr uint8_t a1 = 'B'; std::cout<<std::setw(16)<<a1<<'
';
constexpr auto b1 = htonT(a1); std::cout<<std::setw(16)<<b1<<'
';
constexpr uint16_t a2 = 0x1122; std::cout<<std::setw(16)<<a2<<'
';
constexpr auto b2 = htonT(a2); std::cout<<std::setw(16)<<b2<<'
';
constexpr uint32_t a4 = 0x11223344; std::cout<<std::setw(16)<<a4<<'
';
constexpr auto b4 = htonT(a4); std::cout<<std::setw(16)<<b4<<'
';
constexpr uint64_t a8 = 0x1122334455667788;std::cout<<std::setw(16)<<a8<<'
';
constexpr auto b8 = htonT(a8); std::cout<<std::setw(16)<<b8<<'
';
输出
B
B
1122
2211
11223344
44332211
1122334455667788
8877665544332211
代码生成
在线 C++ 编译器gcc.godbolt.org指示生成的代码。
g++-4.9.2 -std=c++14 -O3
std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT<unsigned char>(unsigned char):
movl %edi, %eax
ret
std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT<unsigned short>(unsigned short):
movl %edi, %eax
rolw $8, %ax
ret
std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT<unsigned int>(unsigned int):
movl %edi, %eax
bswap %eax
ret
std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT<unsigned long>(unsigned long):
movq %rdi, %rax
bswap %rax
ret
clang++-3.5.1 -std=c++14 -O3
std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT<unsigned char>(unsigned char): # @std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT<unsigned char>(unsigned char)
movl %edi, %eax
retq
std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT<unsigned short>(unsigned short): # @std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT<unsigned short>(unsigned short)
rolw $8, %di
movzwl %di, %eax
retq
std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT<unsigned int>(unsigned int): # @std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT<unsigned int>(unsigned int)
bswapl %edi
movl %edi, %eax
retq
std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT<unsigned long>(unsigned long): # @std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT<unsigned long>(unsigned long)
bswapq %rdi
movq %rdi, %rax
retq
注意:我最初的答案不constexpr
符合 C++11 标准。
本答案属于公共领域 CC0 1.0 Universal
解决方案 4:
要检测您的字节序,请使用以下联合:
union {
unsigned long long ull;
char c[8];
} x;
x.ull = 0x0123456789abcdef; // may need special suffix for ULL.
然后您可以检查内容x.c[]
来检测每个字节去了哪里。
为了进行转换,我将使用一次检测代码来查看平台正在使用的字节顺序,然后编写我自己的函数来进行交换。
您可以使其动态化,以便代码可以在任何平台上运行(检测一次,然后使用转换代码中的开关选择正确的转换),但是,如果您只使用一个平台,我只需在单独的程序中执行一次检测,然后编写一个简单的转换例程,确保记录它仅在该平台上运行(或已经过测试)。
以下是我为了说明这一点而编写的一些示例代码。虽然测试得不够彻底,但应该足以让您入门。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TYP_INIT 0
#define TYP_SMLE 1
#define TYP_BIGE 2
static unsigned long long cvt(unsigned long long src) {
static int typ = TYP_INIT;
unsigned char c;
union {
unsigned long long ull;
unsigned char c[8];
} x;
if (typ == TYP_INIT) {
x.ull = 0x01;
typ = (x.c[7] == 0x01) ? TYP_BIGE : TYP_SMLE;
}
if (typ == TYP_SMLE)
return src;
x.ull = src;
c = x.c[0]; x.c[0] = x.c[7]; x.c[7] = c;
c = x.c[1]; x.c[1] = x.c[6]; x.c[6] = c;
c = x.c[2]; x.c[2] = x.c[5]; x.c[5] = c;
c = x.c[3]; x.c[3] = x.c[4]; x.c[4] = c;
return x.ull;
}
int main (void) {
unsigned long long ull = 1;
ull = cvt (ull);
printf ("%llu
",ull);
return 0;
}
请记住,这只是检查纯大/小端顺序。如果你有一些奇怪的变体,其中字节存储顺序,例如 {5,2,3,1,0,7,6,4},cvt()
将会稍微复杂一些。这样的架构不应该存在,但我并不否认我们微处理器行业的朋友的疯狂 :-)
还请记住,这在技术上是未定义的行为,因为您不应该通过除最后写入的字段之外的任何字段访问联合成员。它可能适用于大多数实现,但从纯粹主义者的角度来看,您可能应该咬紧牙关,使用宏来定义自己的例程,例如:
// Assumes 64-bit unsigned long long.
unsigned long long switchOrderFn (unsigned long long in) {
in = (in && 0xff00000000000000ULL) >> 56
| (in && 0x00ff000000000000ULL) >> 40
| (in && 0x0000ff0000000000ULL) >> 24
| (in && 0x000000ff00000000ULL) >> 8
| (in && 0x00000000ff000000ULL) << 8
| (in && 0x0000000000ff0000ULL) << 24
| (in && 0x000000000000ff00ULL) << 40
| (in && 0x00000000000000ffULL) << 56;
return in;
}
#ifdef ULONG_IS_NET_ORDER
#define switchOrder(n) (n)
#else
#define switchOrder(n) switchOrderFn(n)
#endif
解决方案 5:
一些 BSD 系统betoh64
可以满足您的需要。
解决方案 6:
用于小端机器上 64 位交换的一行宏。
#define bswap64(y) (((uint64_t)ntohl(y)) << 32 | ntohl(y>>32))
解决方案 7:
那么通用版本如何呢,它不依赖于输入大小(上面的某些实现假设unsigned long long
是 64 位,但这并不总是正确的):
// converts an arbitrary large integer (preferrably >=64 bits) from big endian to host machine endian
template<typename T> static inline T bigen2host(const T& x)
{
static const int one = 1;
static const char sig = *(char*)&one;
if (sig == 0) return x; // for big endian machine just return the input
T ret;
int size = sizeof(T);
char* src = (char*)&x + sizeof(T) - 1;
char* dst = (char*)&ret;
while (size-- > 0) *dst++ = *src--;
return ret;
}
解决方案 8:
uint32_t SwapShort(uint16_t a)
{
a = ((a & 0x00FF) << 8) | ((a & 0xFF00) >> 8);
return a;
}
uint32_t SwapWord(uint32_t a)
{
a = ((a & 0x000000FF) << 24) |
((a & 0x0000FF00) << 8) |
((a & 0x00FF0000) >> 8) |
((a & 0xFF000000) >> 24);
return a;
}
uint64_t SwapDWord(uint64_t a)
{
a = ((a & 0x00000000000000FFULL) << 56) |
((a & 0x000000000000FF00ULL) << 40) |
((a & 0x0000000000FF0000ULL) << 24) |
((a & 0x00000000FF000000ULL) << 8) |
((a & 0x000000FF00000000ULL) >> 8) |
((a & 0x0000FF0000000000ULL) >> 24) |
((a & 0x00FF000000000000ULL) >> 40) |
((a & 0xFF00000000000000ULL) >> 56);
return a;
}
解决方案 9:
怎么样:
#define ntohll(x) ( ( (uint64_t)(ntohl( (uint32_t)((x << 32) >> 32) )) << 32) |
ntohl( ((uint32_t)(x >> 32)) ) )
#define htonll(x) ntohll(x)
解决方案 10:
我喜欢 union 的答案,非常简洁。通常我只需要位移位来在小端和大端之间转换,尽管我认为 union 解决方案的分配较少,并且可能更快:
//note UINT64_C_LITERAL is a macro that appends the correct prefix
//for the literal on that platform
inline void endianFlip(unsigned long long& Value)
{
Value=
((Value & UINT64_C_LITERAL(0x00000000000000FF)) << 56) |
((Value & UINT64_C_LITERAL(0x000000000000FF00)) << 40) |
((Value & UINT64_C_LITERAL(0x0000000000FF0000)) << 24) |
((Value & UINT64_C_LITERAL(0x00000000FF000000)) << 8) |
((Value & UINT64_C_LITERAL(0x000000FF00000000)) >> 8) |
((Value & UINT64_C_LITERAL(0x0000FF0000000000)) >> 24) |
((Value & UINT64_C_LITERAL(0x00FF000000000000)) >> 40) |
((Value & UINT64_C_LITERAL(0xFF00000000000000)) >> 56);
}
然后,为了检测您是否需要在没有宏魔法的情况下进行翻转,您可以做与 Pax 类似的事情,当将短整型分配给 0x0001 时,在相反的字节序系统上它将是 0x0100。
所以:
unsigned long long numberToSystemEndian
(
unsigned long long In,
unsigned short SourceEndian
)
{
if (SourceEndian != 1)
{
//from an opposite endian system
endianFlip(In);
}
return In;
}
因此,要使用此功能,您需要使用 SourceEndian 作为指示符来传达输入数字的字节序。这可以存储在文件中(如果这是序列化问题),也可以通过网络进行通信(如果这是网络序列化问题)。
解决方案 11:
一个简单的方法是分别对这两个部分使用 ntohl:
unsigned long long htonll(unsigned long long v) {
union { unsigned long lv[2]; unsigned long long llv; } u;
u.lv[0] = htonl(v >> 32);
u.lv[1] = htonl(v & 0xFFFFFFFFULL);
return u.llv;
}
unsigned long long ntohll(unsigned long long v) {
union { unsigned long lv[2]; unsigned long long llv; } u;
u.llv = v;
return ((unsigned long long)ntohl(u.lv[0]) << 32) | (unsigned long long)ntohl(u.lv[1]);
}
解决方案 12:
htonl
可以通过以下步骤完成
如果是大端系统,则直接返回值。无需进行任何转换。如果是小端系统,则需要进行以下转换。
取 LSB 32 位并应用“htonl”并移位 32 次。
取 MSB 32 位(通过将 uint64_t 值向右移动 32 次)并应用“htonl”
现在对第 2 步和第 3 步中收到的值进行按位或运算。
同样地ntohll
,对于
#define HTONLL(x) ((1==htonl(1)) ? (x) : (((uint64_t)htonl((x) & 0xFFFFFFFFUL)) << 32) | htonl((uint32_t)((x) >> 32)))
#define NTOHLL(x) ((1==ntohl(1)) ? (x) : (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32)))
您也可以将以上 2 个定义声明为函数。
解决方案 13:
template <typename T>
static T ntoh_any(T t)
{
static const unsigned char int_bytes[sizeof(int)] = {0xFF};
static const int msb_0xFF = 0xFF << (sizeof(int) - 1) * CHAR_BIT;
static bool host_is_big_endian = (*(reinterpret_cast<const int *>(int_bytes)) & msb_0xFF ) != 0;
if (host_is_big_endian) { return t; }
unsigned char * ptr = reinterpret_cast<unsigned char *>(&t);
std::reverse(ptr, ptr + sizeof(t) );
return t;
}
适用于 2 字节、4 字节、8 字节和 16 字节(如果您有 128 位整数)。应独立于操作系统/平台。
解决方案 14:
假设你正在使用 64 位操作系统的 Linux 上编码;大多数系统都有htole(x)
或等,这些通常是各种ntobe(x)
的宏bswap
#include <endian.h>
#include <byteswap.h>
unsigned long long htonll(unsigned long long val)
{
if (__BYTE_ORDER == __BIG_ENDIAN) return (val);
else return __bswap_64(val);
}
unsigned long long ntohll(unsigned long long val)
{
if (__BYTE_ORDER == __BIG_ENDIAN) return (val);
else return __bswap_64(val);
}
附注:这些只是用来交换字节顺序的函数。例如,如果您在大端网络中使用小端,但如果您使用大端编码,那么这将不必要地反转字节顺序,因此if __BYTE_ORDER == __LITTLE_ENDIAN
可能需要进行一些“”检查,以使您的代码更具可移植性,具体取决于您的需求。
更新:编辑以显示字节序检查的示例
解决方案 15:
适合任何值大小的通用函数。
template <typename T>
T swap_endian (T value)
{
union {
T src;
unsigned char dst[sizeof(T)];
} source, dest;
source.src = value;
for (size_t k = 0; k < sizeof(T); ++k)
dest.dst[k] = source.dst[sizeof(T) - k - 1];
return dest.src;
}
解决方案 16:
union help64
{
unsigned char byte[8];
uint64_t quad;
};
uint64_t ntoh64(uint64_t src)
{
help64 tmp;
tmp.quad = src;
uint64_t dst = 0;
for(int i = 0; i < 8; ++i)
dst = (dst << 8) + tmp.byte[i];
return dst;
}
解决方案 17:
通常,将主机整数转换为网络顺序时,无需知道机器的字节顺序。不幸的是,这仅在以字节而不是另一个整数形式写出网络顺序值时才成立:
static inline void short_to_network_order(uchar *output, uint16_t in)
{
output[0] = in>>8&0xff;
output[1] = in&0xff;
}
(根据需要扩展以获得更大的数字)。
这将 (a) 适用于任何架构,因为我从未使用过有关整数在内存中布局方式的特殊知识,并且 (b) 应该主要在 big-endian 架构中进行优化,因为现代编译器并不愚蠢。
当然,缺点是,这不是与 htonl() 及其朋友相同的标准接口(我不认为这是一个缺点,因为 htonl() 的设计在我看来是一个糟糕的选择)。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件