如何在C++中转换大端和小端值?
为了清晰起见,我需要将二进制数据(双精度浮点值和32位、64位整数)从一种CPU架构转换为另一种。这不涉及网络,因此ntoh()和类似的函数在这里无法使用。
注意:我接受的答案直接适用于我正在针对的编译器(这就是我选择它的原因)。然而,这里还有其他非常好的、更可移植的答案。
如何在C++中转换大端和小端值?
为了清晰起见,我需要将二进制数据(双精度浮点值和32位、64位整数)从一种CPU架构转换为另一种。这不涉及网络,因此ntoh()和类似的函数在这里无法使用。
注意:我接受的答案直接适用于我正在针对的编译器(这就是我选择它的原因)。然而,这里还有其他非常好的、更可移植的答案。
如果您正在使用 Visual C++,请执行以下操作:包含 intrin.h 并调用以下函数:
对于 16 位数字:
unsigned short _byteswap_ushort(unsigned short value);
对于32位数字:
unsigned long _byteswap_ulong(unsigned long value);
对于64位数字:
unsigned __int64 _byteswap_uint64(unsigned __int64 value);
8位数字(字符)无需转换。
同时,这些仅对无符号值定义,也适用于有符号整数。
对于浮点数和双精度浮点数,情况会更加复杂,因为与普通整数一样,它们可能或可能不在主机字节顺序中。在大端机器上可以获得小端浮点数,反之亦然。
其他编译器也有类似的内置函数。
例如,在GCC中,您可以直接调用此处记录的一些内建函数:
uint32_t __builtin_bswap32 (uint32_t x)
uint64_t __builtin_bswap64 (uint64_t x)
(不需要包含任何内容)。据我所知,bits.h 以非GCC为中心的方式声明了相同的函数。
16位交换只是位旋转。
调用内在函数而不是自己滚动可以获得最佳性能和代码密度。
__builtin_bswapX
只在 GCC-4.3 或更新的版本中才可用。 - Matt Joinerhtonl
、htons
等函数。您必须根据特定情境来确定何时需要交换字节顺序。 - Brian Vandenbergportable_endian.h
,Windows+MSVC可以针对大端xbox360,但我不完全推荐这种方法,因为它甚至在Windows上也会使用ntohl
等非内联调用Winsock DLL。无论如何,在可移植的C++程序中,检测何时进行字节交换是另一个难点,因为据我所知,ISO C++标准没有定义用于主机字节序检测的宏。只需提供一个好的SO Q&A链接即可。 - Peter Cordes简单来说:
#include <climits>
template <typename T>
T swap_endian(T u)
{
static_assert (CHAR_BIT == 8, "CHAR_BIT != 8");
union
{
T u;
unsigned char u8[sizeof(T)];
} source, dest;
source.u = u;
for (size_t k = 0; k < sizeof(T); k++)
dest.u8[k] = source.u8[sizeof(T) - k - 1];
return dest.u;
}
usage: swap_endian<uint32_t>(42)
.
来自Rob Pike的字节顺序谬论:
假设你的数据流有一个小端编码的32位整数。以下是提取它的方法(假设使用无符号字节):
i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | ((unsigned)data[3]<<24);
i = (data[3]<<0) | (data[2]<<8) | (data[1]<<16) | ((unsigned)data[0]<<24);
int
和unsigned int
为32位,否则类型可能需要调整。unsigned
,因为默认情况下它会被提升为int
,并且24位的移位意味着操作未定义的符号位。如果您是出于网络/主机兼容性目的进行此操作,应使用:
ntohl() //Network to Host byte order (Long)
htonl() //Host to Network byte order (Long)
ntohs() //Network to Host byte order (Short)
htons() //Host to Network byte order (Short)
如果你是出于其他原因这样做的话,这里提供的任一字节交换解决方案都可以正常工作。
htonl
和ntohl
函数无法转换为小端格式。 - Brian Vandenberg#include <boost/type_traits.hpp>
#include <boost/static_assert.hpp>
#include <boost/detail/endian.hpp>
#include <stdexcept>
#include <cstdint>
enum endianness
{
little_endian,
big_endian,
network_endian = big_endian,
#if defined(BOOST_LITTLE_ENDIAN)
host_endian = little_endian
#elif defined(BOOST_BIG_ENDIAN)
host_endian = big_endian
#else
#error "unable to determine system endianness"
#endif
};
namespace detail {
template<typename T, size_t sz>
struct swap_bytes
{
inline T operator()(T val)
{
throw std::out_of_range("data size");
}
};
template<typename T>
struct swap_bytes<T, 1>
{
inline T operator()(T val)
{
return val;
}
};
template<typename T>
struct swap_bytes<T, 2>
{
inline T operator()(T val)
{
return ((((val) >> 8) & 0xff) | (((val) & 0xff) << 8));
}
};
template<typename T>
struct swap_bytes<T, 4>
{
inline T operator()(T val)
{
return ((((val) & 0xff000000) >> 24) |
(((val) & 0x00ff0000) >> 8) |
(((val) & 0x0000ff00) << 8) |
(((val) & 0x000000ff) << 24));
}
};
template<>
struct swap_bytes<float, 4>
{
inline float operator()(float val)
{
uint32_t mem =swap_bytes<uint32_t, sizeof(uint32_t)>()(*(uint32_t*)&val);
return *(float*)&mem;
}
};
template<typename T>
struct swap_bytes<T, 8>
{
inline T operator()(T val)
{
return ((((val) & 0xff00000000000000ull) >> 56) |
(((val) & 0x00ff000000000000ull) >> 40) |
(((val) & 0x0000ff0000000000ull) >> 24) |
(((val) & 0x000000ff00000000ull) >> 8 ) |
(((val) & 0x00000000ff000000ull) << 8 ) |
(((val) & 0x0000000000ff0000ull) << 24) |
(((val) & 0x000000000000ff00ull) << 40) |
(((val) & 0x00000000000000ffull) << 56));
}
};
template<>
struct swap_bytes<double, 8>
{
inline double operator()(double val)
{
uint64_t mem =swap_bytes<uint64_t, sizeof(uint64_t)>()(*(uint64_t*)&val);
return *(double*)&mem;
}
};
template<endianness from, endianness to, class T>
struct do_byte_swap
{
inline T operator()(T value)
{
return swap_bytes<T, sizeof(T)>()(value);
}
};
// specialisations when attempting to swap to the same endianess
template<class T> struct do_byte_swap<little_endian, little_endian, T> { inline T operator()(T value) { return value; } };
template<class T> struct do_byte_swap<big_endian, big_endian, T> { inline T operator()(T value) { return value; } };
} // namespace detail
template<endianness from, endianness to, class T>
inline T byte_swap(T value)
{
// ensure the data is only 1, 2, 4 or 8 bytes
BOOST_STATIC_ASSERT(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8);
// ensure we're only swapping arithmetic types
BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value);
return detail::do_byte_swap<from, to, T>()(value);
}
然后您可以按照以下方式使用它:
// swaps val from host-byte-order to network-byte-order
auto swapped = byte_swap<host_endian, network_endian>(val);
反之亦然
// swap a value received from the network into host-byte-order
auto val = byte_swap<network_endian, host_endian>(val_from_network);
从大端序到小端序的过程和从小端序到大端序的过程是相同的。
以下是一些示例代码:
void swapByteOrder(unsigned short& us)
{
us = (us >> 8) |
(us << 8);
}
void swapByteOrder(unsigned int& ui)
{
ui = (ui >> 24) |
((ui<<8) & 0x00FF0000) |
((ui>>8) & 0x0000FF00) |
(ui << 24);
}
void swapByteOrder(unsigned long long& ull)
{
ull = (ull >> 56) |
((ull<<40) & 0x00FF000000000000) |
((ull<<24) & 0x0000FF0000000000) |
((ull<<8) & 0x000000FF00000000) |
((ull>>8) & 0x00000000FF000000) |
((ull>>24) & 0x0000000000FF0000) |
((ull>>40) & 0x000000000000FF00) |
(ull << 56);
}
有一种汇编指令叫做 BSWAP,可以帮助您 极快地 进行交换。您可以在此处了解详情。
Visual Studio,或者更精确地说是 Visual C++ 运行时库,提供了平台内置函数来实现此功能,分别为 _byteswap_ushort()、_byteswap_ulong() 和 _byteswap_int64()
。其他平台应该也有类似的函数,但我不知道它们具体的名称。
我们已经通过模板完成了这个。你可以像这样做:
// Specialization for 2-byte types.
template<>
inline void endian_byte_swapper< 2 >(char* dest, char const* src)
{
// Use bit manipulations instead of accessing individual bytes from memory, much faster.
ushort* p_dest = reinterpret_cast< ushort* >(dest);
ushort const* const p_src = reinterpret_cast< ushort const* >(src);
*p_dest = (*p_src >> 8) | (*p_src << 8);
}
// Specialization for 4-byte types.
template<>
inline void endian_byte_swapper< 4 >(char* dest, char const* src)
{
// Use bit manipulations instead of accessing individual bytes from memory, much faster.
uint* p_dest = reinterpret_cast< uint* >(dest);
uint const* const p_src = reinterpret_cast< uint const* >(src);
*p_dest = (*p_src >> 24) | ((*p_src & 0x00ff0000) >> 8) | ((*p_src & 0x0000ff00) << 8) | (*p_src << 24);
}
short big = 0xdead;
short little = (((big & 0xff)<<8) | ((big & 0xff00)>>8));
你还可以声明一个无符号字符向量,将输入值memcpy到其中,将字节反转到另一个向量中,并将字节memcpy出来,但这比位操作要慢几个数量级,特别是对于64位值。
如果您正在尝试在不同平台之间传输数据,请查看 ntoh 和 hton 函数。
short swap(short x)
这样的代码时,我都会感到不安,因为它会在移动到具有不同字节序的平台时出现问题。Matthieu M 在下面给出了唯一正确的答案。 - Mark Lakata