如何在C++中转换大端和小端值?

265

如何在C++中转换大端和小端值?

为了清晰起见,我需要将二进制数据(双精度浮点值和32位、64位整数)从一种CPU架构转换为另一种。这不涉及网络,因此ntoh()和类似的函数在这里无法使用。


注意:我接受的答案直接适用于我正在针对的编译器(这就是我选择它的原因)。然而,这里还有其他非常好的、更可移植的答案。


27
ntoh和hton可以很好地运作,即使它们与网络无关。 - Ben Collins
2
处理字节序的最佳方式是确保代码在小端和大端主机上都能运行。如果可以实现,那么你可能做得很对了。假设你在x86/be上运行是一种危险的做法。 - jakobengblom2
14
如果机器是大端的,hton ntoh将无法工作,因为问题的提问者明确希望执行转换。 - fabspro
8
@jakobengblom2 是唯一提到这个的人。这个页面上几乎所有的例子都使用“交换”字节的概念,而不是针对底层字节序进行中立处理。如果你正在处理外部文件格式(这些格式具有明确定义的字节序),那么最可移植的方法是将外部数据视为字节流,并将字节流转换为本机整数格式并且在需要时再转回字节流。每当我看到 short swap(short x) 这样的代码时,我都会感到不安,因为它会在移动到具有不同字节序的平台时出现问题。Matthieu M 在下面给出了唯一正确的答案。 - Mark Lakata
5
你完全错误地思考了这个问题。任务不是“我如何在大端和小端值之间进行转换”,而是“我如何将特定格式的浮点数和整数值转换为我的平台本地格式”。如果做得正确,本地格式可以是大端、小端、混合端或三进制,对于你的代码来说都无关紧要。 - David Schwartz
显示剩余3条评论
35个回答

219

如果您正在使用 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位交换只是位旋转。

调用内在函数而不是自己滚动可以获得最佳性能和代码密度。


12
使用GCC编译器时,我可能会使用以下代码:#include <byteswap.h>int32_t bswap_32(int32_t x)int64_t bswap_64(int64_t x) - jmanning2k
5
__builtin_bswapX 只在 GCC-4.3 或更新的版本中才可用。 - Matt Joiner
23
值得注意的是,这些指令总是交换字节顺序,它们不像htonlhtons等函数。您必须根据特定情境来确定何时需要交换字节顺序。 - Brian Vandenberg
11
@Jason 因为8位数字在大端序和小端序中是相同的。 :-) - Nils Pipenbrinck
3
这个答案应该真正讲一些关于如何检测你是否在大端主机上的内容。根据这个尝试编写的portable_endian.h,Windows+MSVC可以针对大端xbox360,但我不完全推荐这种方法,因为它甚至在Windows上也会使用ntohl等非内联调用Winsock DLL。无论如何,在可移植的C++程序中,检测何时进行字节交换是另一个难点,因为据我所知,ISO C++标准没有定义用于主机字节序检测的宏。只需提供一个好的SO Q&A链接即可。 - Peter Cordes
显示剩余9条评论

114

简单来说:

#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).


5
给你点赞。我刚刚使用了uchars,并将4分配给1,3分配给2,2分配给3,1分配给4,但如果您有不同的大小,则此方法更加灵活。我记得第一代奔腾处理器上有6个时钟。BSWAP是1个时钟,但它是平台特定的。 - user1899861
2
@RocketRoy:是的,如果速度成为一个问题,使用平台和类型特定的内在函数进行重载非常简单。 - Alexandre C.
4
这种通过字符数组进行类型转换的联合使用在标准中是明确允许的。例如,请参见此问题。 - Alexandre C.
6
这段话的意思是:“在C++标准中没有定义这种语法,只有在C语言中才有。在C++中(这段代码使用的是C++),这个代码是未定义行为。” - Rapptz
5
@Rapptz说3.10看起来很清楚:“如果程序试图通过除以下类型之一的glvalue访问对象的存储值,则行为未定义:[...] char或unsigned char类型”。也许我漏掉了什么,但对我来说很清楚,可以通过char指针访问任何类型。 - Alexandre C.
显示剩余9条评论

95

来自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);

TL;DR:不用担心平台本地顺序,重要的是你从中读取的流的字节顺序,并且最好希望它被很好地定义。
注意1:这里期望intunsigned int为32位,否则类型可能需要调整。
注意2:在移位之前,必须将最后一个字节显式转换为unsigned,因为默认情况下它会被提升为int,并且24位的移位意味着操作未定义的符号位。

11
这很酷,但我觉得它似乎只适用于整数及其变体。那浮点数/双精度浮点数该怎么办? - Brett
1
@v.oddou:是的和不是,内存映射文件与网络帧完全相同;如果您接受不要直接读取它们,则所有重要的事情都在于它们的字节顺序:如果是小端序,请使用第一个公式,如果是大端序,则使用第二个。任何值得一试的编译器都会优化不需要的转换,如果字节顺序匹配。 - Matthieu M.
2
@meowsqueak:是的,我认为它可以工作,因为只有字节的顺序改变了,而不是每个字节内部的位的顺序。 - Matthieu M.
4
有一个相关的话题,链接的帖子看起来让人不太舒服。这个人似乎重视简洁,但他却选择写一篇长篇大论,诉说所有那些程序员的缺陷,因为他们在字节序方面不如他那么开明。相反,他应该解释一下情况,以及为什么他的解决方案总是有效的。 - Ad N
1
@Kenji:我之前不知道uint8_t,所以才会这样说;我的主要观点是使用unsigned。我之前尝试过没有使用unsigned的答案,结果得到了一些奇怪的答案。 - joseph
显示剩余19条评论

62

如果您是出于网络/主机兼容性目的进行此操作,应使用:

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)

如果你是出于其他原因这样做的话,这里提供的任一字节交换解决方案都可以正常工作。


5
我相信网络字节顺序是大端序。即使您不使用网络代码,也可以考虑这些函数。但是,没有浮点版本的 ntohf 或 htonf。 - hookenz
3
Matt H.的说法大体上是正确的,但并不是所有的计算机系统都采用小端字节顺序。如果你正在处理摩托罗拉68k、PowerPC或其他大端架构,这些函数将不会交换字节,因为它们已经处于“网络字节顺序”中。 - Frosty
4
遗憾的是,在大端平台中,htonlntohl函数无法转换为小端格式。 - Brian Vandenberg
2
@BrianVandenberg:这不是它们的本意。它们的目的是提供一致的外部格式。我认为,除非你实际上正在实现这些函数,否则你通常不应该关心这个格式到底是什么。 - celtschk
4
为了避免不可避免的问题:需要在BE平台上使用LE的原因有很多;许多文件格式(如bmp、fli、pcx、qtm、rtf、tga等)使用小端值……或者至少这些格式的某个版本曾经使用过。 - Brian Vandenberg
显示剩余7条评论

29
我从这篇帖子中汲取了一些建议,将它们结合起来形成了以下内容:

#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);

1
你还需要包含 <cstdint> 或 <stdint.h>,例如 uint32_t。 - ady

22

从大端序到小端序的过程和从小端序到大端序的过程是相同的。

以下是一些示例代码:

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);
}

2
这里发布的最后一个函数是不正确的,应该进行编辑:void swapByteOrder(unsigned long long& ull) { ull = (ull >> 56) | ... (ull << 56); } - Eric Burnett
15
使用逻辑与(&&)而非按位与(&)是不正确的。根据 C++ 规范,两个操作数会被隐式转换为布尔类型,这并不是你想要的结果。请使用按位与(&)。 - Trevor Robinson

17

有一种汇编指令叫做 BSWAP,可以帮助您 极快地 进行交换。您可以在此处了解详情

Visual Studio,或者更精确地说是 Visual C++ 运行时库,提供了平台内置函数来实现此功能,分别为 _byteswap_ushort()、_byteswap_ulong() 和 _byteswap_int64()。其他平台应该也有类似的函数,但我不知道它们具体的名称。


这是一个很棒的链接。它重新点燃了我对x86汇编语言的兴趣。 - PP.
1
这里展示了BSWAP的计时结果。http://gmplib.org/~tege/x86-timing.pdf... 还有这里 ...http://www.agner.org/optimize/instruction_tables.pdf - user1899861

12

我们已经通过模板完成了这个。你可以像这样做:

// 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);
}

9
跟在C语言中一样:
short big = 0xdead;
short little = (((big & 0xff)<<8) | ((big & 0xff00)>>8));

你还可以声明一个无符号字符向量,将输入值memcpy到其中,将字节反转到另一个向量中,并将字节memcpy出来,但这比位操作要慢几个数量级,特别是对于64位值。


8

如果您正在尝试在不同平台之间传输数据,请查看 ntoh 和 hton 函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接