64 bit ntohl() in C++?
我的问题是我需要将64位整数(在我的情况下,这是一个无符号的long long)从big endian转换为little endian。现在,我需要进行特定的转换。但是如果目标平台为大端字节序,该函数(如
我可以使用什么?我想要一些标准的东西(如果存在的话),但我愿意接受实施建议。我已经看到过去使用联合完成这种类型的转换。我想我可以有一个unsigned long long和一个char [8]的联合。然后相应地交换字节。 (显然会在大端字节序的平台上中断)。
文档:Linux(glibc> = 2.9)或FreeBSD上的
不幸的是,在2009年,OpenBSD,FreeBSD和glibc(Linux)并没有很好地协同工作来为此创建一个(非内核API)libc标准。
当前,这段简短的预处理器代码:
1 2 3 4 5 6 7 8 9 10 | #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风格的宏。
使用示例:
1 2 3 4 5 6 7 | #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库"式方法。
我建议阅读以下内容:http://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | #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 2 3 | 1 72057594037927936 1 |
如果删除高4个字节,则可以使用ntohl()进行测试。
您也可以将其转换为C ++中很好的模板化函数,该函数可以在任何大小的整数上工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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位安全了!
要检测您的字节序,请使用以下联合:
1 2 3 4 5 | union { unsigned long long ull; char c[8]; } x; x.ull = 0x0123456789abcdef; // may need special suffix for ULL. |
然后,您可以检查
为了进行转换,我将使用该检测代码一次来查看平台使用的字节序,然后编写我自己的函数进行交换。
您可以使它动态化,以便代码可以在任何平台上运行(检测一次,然后在转换代码内使用开关来选择正确的转换),但是,如果您只打算使用一个平台,则只需在单独的程序中进行一次检测,然后编写一个简单的转换例程,以确保您记录它仅在该平台上运行(或已经过测试)。
这是我摘录的一些示例代码。尽管没有进行全面的测试,但是应该足以使您入门。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #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}的顺序存储字节,则
还请记住,这在技术上是不确定的行为,因为除了最后一个写入的字段外,您不应通过任何其他字段访问工会成员。它可能适用于大多数实现,但从纯粹的角度来看,您可能应该硬着头皮,使用宏定义自己的例程,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 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 |
有些BSD系统具有
快速回答
1 2 3 4 5 6 7 8 | #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所报告的(请参阅她/他的评论),
如果您想分享C ++优雅的技巧来检测字节序,请编辑此答案。
可移植性
此外,宏
1 2 3 4 5 6 7 8 | #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 |
另请参阅更可移植的源代码:跨平台
C ++ 14
通用
1 2 3 4 5 6 7 8 9 10 11 12 | #include <endian.h> // __BYTE_ORDER __LITTLE_ENDIAN #include // 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 ++ 11
-
C ++ 11不允许在
constexpr 函数中使用局部变量。
因此,诀窍是使用具有默认值的参数。 -
此外,C ++ 11
constexpr 函数必须包含一个单个表达式。
因此,主体由一个返回值组成,该返回值带有一些逗号分隔的语句。
1 2 3 4 5 6 7 8 9 10 | 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; } |
使用
(请参阅编译并在coliru上运行输出)。
C ++ 11
但是,以上版本不允许将
1 | constexpr int32_t hton_six = htonT( int32_t(6) ); |
最后,我们需要根据16/32/64位来分离(专门化)功能。
但是我们仍然可以保留泛型函数。
(请参阅coliru上的完整代码段)
下面的C ++ 11代码段使用特征
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 } |
第一版测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 <<' '; |
第二版测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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<<' '; |
输出量
1 2 3 4 5 6 7 8 | B B 1122 2211 11223344 44332211 1122334455667788 8877665544332211 |
代码生成
在线C ++编译器gcc.godbolt.org指示生成的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 |
注意:我的原始答案不符合C ++ 11-
此答案在Public Domain CC0 1.0 Universal中 sub>
一个行宏,可在小型字节序计算机上进行64位交换。
1 | #define bswap64(y) (((uint64_t)ntohl(y)) << 32 | ntohl(y>>32)) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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; } |
不依赖于输入大小的通用版本怎么样(上面的某些实现假定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 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; } |
怎么样:
1 2 3 | #define ntohll(x) ( ( (uint64_t)(ntohl( (uint32_t)((x << 32) >> 32) )) << 32) | ntohl( ((uint32_t)(x >> 32)) ) ) #define htonll(x) ntohll(x) |
一种简单的方法是分别在两个部分上使用ntohl:
1 2 3 4 5 6 7 8 9 10 11 12 | 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]); } |
我喜欢工会的回答,非常简洁。尽管我认为联合解决方案的分配较少,并且可能更快,但通常我只是在小端和大端之间转换而已:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //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的操作,当将short分配给0x0001时,在相反的endian系统上它将为0x0100。
所以:
1 2 3 4 5 6 7 8 9 10 11 12 13 | unsigned long long numberToSystemEndian ( unsigned long long In, unsigned short SourceEndian ) { if (SourceEndian != 1) { //from an opposite endian system endianFlip(In); } return In; } |
因此,要使用此功能,您需要将SourceEndian用作指示符,以传达输入数字的字节序。这可以存储在文件中(如果这是序列化问题),也可以通过网络进行通信(如果这是网络序列化问题)。
- 如果其大端系统直接返回该值。无需进行任何转换。如果是小型字节序系统,则需要执行以下转换。
- 取32位LSB并应用" htonl"并移位32次。
- 取MSB 32位(通过将uint64_t值右移32次)并应用'htonl'
- 现在,对在第二步和第三步中接收到的值应用按位或。
同样对于
1 2 | #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个定义的函数。
1 2 3 4 5 6 7 8 9 10 11 12 | 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位整数)。应与操作系统/平台无关。
假设您正在使用64位OS的Linux上进行编码;大多数系统具有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #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); } |
边注;这些只是调用以交换字节顺序的函数。例如,如果在大端网络中使用Little Endian,但是如果使用大端编码,则这将不必要地颠倒字节顺序,因此可能需要进行一些"
更新:编辑以显示字节序检查的示例
任何值大小的通用功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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; } |
通常无需知道机器的字节序即可将主机整数转换为网络顺序。不幸的是,只有当您以字节(而不是另一个整数)形式写出净订单值时,这种情况才成立:
1 2 3 4 5 | static inline void short_to_network_order(uchar *output, uint16_t in) { output[0] = in>>8&0xff; output[1] = in&0xff; } |
(根据需要扩展为更大的数字)。
这将(a)在任何体系结构上都有效,因为在任何时候我都不会使用有关整数在内存中的布局方式的专门知识,并且(b)在现代大编译器体系结构中,由于现代编译器并不笨拙,因此应该在大多数情况下对其进行优化。
缺点当然是,它与htonl()和friends的标准接口不同(我不认为这是缺点,因为htonl()的设计不是imo的选择)。