【问题标题】:How do I convert between big-endian and little-endian values in C++?如何在 C++ 中的大端和小端值之间进行转换?
【发布时间】:2010-09-11 10:33:13
【问题描述】:

如何在 C++ 中的大端和小端值之间进行转换?

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


注意:我接受的答案直接适用于我所针对的编译器(这就是我选择它的原因)。但是,这里还有其他非常好的、更便携的答案。

【问题讨论】:

  • ntoh hton 可以正常工作,即使它与网络没有任何关系。
  • 一般处理字节序的最佳方法是确保代码在小端和大端主机上运行。如果这有效,那么您可能做对了。假设您使用的是 x86/be,这种做法很危险。
  • hton ntoh 如果机器是 big-endian 将不起作用,因为提问者明确想要执行转换。
  • @jakobengblom2 是唯一提到这一点的人。此页面上的几乎所有示例都使用诸如“交换”字节之类的概念,而不是与底层字节序无关。如果您正在处理外部文件格式(具有明确定义的字节序),那么最便携的做法是将外部数据视为字节流,并将字节流与本机整数转换。每次看到short swap(short x) 代码时我都会畏缩,因为如果您移动到具有不同字节序的平台,它会中断。 Matthieu M 在下面给出了唯一正确的答案。
  • 你对问题的思考完全错了。任务不是“我如何在大端和小端值之间进行转换”。任务是“如何将特定格式的浮点和整数值转换为我平台的本机格式”。如果你做对了,原生格式可以是大端、小端、混合端或三进制,以满足你所有的代码需求。

标签: c++ endianness


【解决方案1】:

如果您使用的是 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中可以直接调用some builtins as documented here:

uint32_t __builtin_bswap32 (uint32_t x)
uint64_t __builtin_bswap64 (uint64_t x)

(无需包含某些内容)。 Afaik bits.h 也以非 gcc 为中心的方式声明了相同的函数。

16 位交换它只是一个位旋转。

调用内部函数而不是自己滚动可以为您提供最佳性能和代码密度顺便说一句..

【讨论】:

  • 使用 GCC,我可能会使用:#include int32_t bswap_32(int32_t x) int64_t bswap_64(int64_t x)
  • __builtin_bswapX 只能从 GCC-4.3 开始使用
  • 还值得注意的是,这些内在函数 /always/ 交换字节,它们不像 htonlhtons 等。你必须根据你的情况知道何时实际交换字节。
  • @Jason 因为 8 位数字在大端和小端中是相同的。 :-)
  • 这个答案应该真正说明检测您是否在大端主机上。 (Windows+MSVC 可以针对大端 xbox360,根据this attempt at portable_endian.h,我不完全推荐它,因为它使用ntohl 等等,即使在 Windows 上它是对 Winsock DLL 的非内联调用)。无论如何,检测何时进行字节交换是可移植 C++ 程序中的另一个难题,因为 AFAIK ISO C++ 标准没有定义用于主机字节顺序检测的宏。只需一个链接到一个很好的 SO Q&A 就可以了。
【解决方案2】:

简单地说:

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

用法:swap_endian&lt;uint32_t&gt;(42)

【讨论】:

  • 点个赞。我只是使用了 uchars,并分配了 4 到 1、3 到 2、2 到 3 和 1 到 4,但是如果你有不同的大小,这会更灵活。第一代奔腾 IIRC 上有 6 个时钟。 BSWAP 是 1 个时钟,但特定于平台。
  • @RocketRoy:是的,如果速度成为问题,那么使用特定于平台和类型的内部函数编写重载非常简单。
  • @MihaiTodor:标准明确允许使用联合来通过字符数组进行类型转换。参见例如。 this question.
  • @AlexandreC。不在 C++ 标准中——仅在 C 中。在 C++(此代码是)中,此代码是未定义的行为。
  • @Rapptz: 3.10 似乎很清楚:“如果程序尝试通过以下类型之一的左值访问对象的存储值,则行为未定义:[...] char 或 unsigned char 类型。”。也许我在这里遗漏了一些东西,但我很清楚明确允许通过 char 指针访问任何类型。
【解决方案3】:

来自 Rob Pike 的 The Byte Order Fallacy

假设您的数据流有一个小端编码的 32 位整数。以下是如何提取它(假设是无符号字节):

i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);

如果它是大端的,那么提取它的方法如下:

i = (data[3]<<0) | (data[2]<<8) | (data[1]<<16) | (data[0]<<24);

TL;DR:不用担心您的平台原生顺序,重要的是您正在读取的流的字节顺序,您最好希望它定义明确。

注意:注释中指出,在没有显式类型转换的情况下,dataunsigned charuint8_t 的数组很重要。使用 signed charchar(如果有符号)将导致 data[x] 被提升为整数,data[x] &lt;&lt; 24 可能会将 1 移入符号位,即 UB。

【讨论】:

  • 这很酷,但在我看来它只适用于整数和变体。如何处理浮点数/双精度数?
  • @v.oddou: 是与否,内存映射文件与网络帧完全相同;如果您接受 not 直接阅读它们,那么重要的是 their 字节序:如果是 little-endian,则使用第一个公式,如果是 big-endian,则使用第二个。如果字节序匹配,任何值得其盐的编译器都会优化掉不需要的转换。
  • @meowsqueak:是的,我希望它确实有效,因为只有字节的顺序会改变,而不是每个字节中的位顺序。
  • 在一个松散相关的注释上,链接的帖子读起来有些不愉快……这家伙似乎很重视简洁,但他更喜欢写一篇长篇大论的文章,​​对所有那些没有那么开明的糟糕程序员他关注的是字节顺序,而不是实际解释情况以及为什么他的解决方案总是有效。
  • 如果您使用此方法,请确保将数据转换为 (unsigned char*)
【解决方案4】:

如果您这样做是为了网络/主机兼容性,您应该使用:

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)

如果您出于其他原因这样做,这里介绍的 byte_swap 解决方案之一可以正常工作。

【讨论】:

  • 我相信网络字节排序是大端。即使您不使用网络代码,也可以考虑到这些功能。但是没有浮动版本 ntohf 或 htonf
  • Matt H. 大部分是正确的。并非所有计算机系统都具有 little-endian 字节顺序。如果您正在研究,比如 motorolla 68k、PowerPC 或其他大端架构,这些函数根本不会交换字节,因为它们已经处于“网络字节顺序”中。
  • 很遗憾,htonlntohl 在大端平台上无法使用小端。
  • @celtschk,明白了;但是,OP 想要一种切换字节顺序的方法,即使在大端环境中也是如此。
  • 为了避免不可避免的问题:BE 平台需要 LE 的原因有很多;许多文件格式(bmp、fli、pcx、qtm、rtf、tga 等等)都使用小端值……或者至少,某种版本的格式曾经使用过。
【解决方案5】:

我从这篇文章中得到了一些建议,并将它们组合在一起形成了这个:

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

【讨论】:

  • 您还必须包含 ,例如,对于 uint32_t
【解决方案6】:

从大端到小端的过程与从小端到大端的过程相同。

下面是一些示例代码:

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

【讨论】:

  • 这里贴的最后一个函数不正确,应该编辑为:void swapByteOrder(unsigned long long& ull) { ull = (ull >> 56) | ... (ull
  • 我认为使用逻辑与 (&&) 而不是按位与 (&) 是不正确的。根据 C++ 规范,两个操作数都被隐式转换为 bool,这不是你想要的。
【解决方案7】:

有一个名为 BSWAP 的汇编指令会为您完成交换,非常快。 你可以阅读它here

Visual Studio,或更准确地说是 Visual C++ 运行时库,为此具有平台内在函数,称为 _byteswap_ushort(), _byteswap_ulong(), and _byteswap_int64()。其他平台应该也有类似的,但我不知道它们会被称为什么。

【讨论】:

【解决方案8】:

我们已经使用模板完成了这项工作。你可以这样做:

// 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】:

    如果您这样做是为了在不同平台之间传输数据,请查看 ntoh 和 hton 函数。

    【讨论】:

      【解决方案10】:

      与 C 中的方法相同:

      short big = 0xdead;
      short little = (((big & 0xff)<<8) | ((big & 0xff00)>>8));
      

      您还可以声明一个无符号字符向量,将输入值 memcpy 到其中,将字节反转为另一个向量并 memcpy 出字节,但这比位旋转要长几个数量级,尤其是 64-位值。

      【讨论】:

        【解决方案11】:

        在大多数 POSIX 系统上(因为它不在 POSIX 标准中),都有 endian.h,它可用于确定您的系统使用什么编码。从那里开始是这样的:

        unsigned int change_endian(unsigned int x)
        {
            unsigned char *ptr = (unsigned char *)&x;
            return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
        }
        

        这会交换顺序(从大端到小端):

        如果您有数字 0xDEADBEEF(在存储为 0xEFBEADDE 的小端系统上),ptr[0] 将为 0xEF,ptr[1] 为 0xBE,等等。

        但如果你想将它用于网络,那么 htons、htonl 和 htonll(以及它们的倒数 ntohs、ntohl 和 ntohll)将有助于从主机顺序转换为网络顺序。

        【讨论】:

        • 这很有趣 - opengroup.org/onlinepubs/9699919799/toc.htm 的 POSIX 标准没有提到标题 '`。
        • 您可以使用htonl 和朋友,无论用例是否与网络有关。网络字节顺序是 big-endian,因此只需将这些函数视为 host_to_be 和 be_to_host。 (不过,如果您需要 host_to_le,则无济于事。)
        【解决方案12】:

        请注意,至少对于 Windows,htonl() 比它们固有的对应 _byteswap_ulong() 慢得多。前者是对ws2_32.dll 的DLL 库调用,后者是一条BSWAP 汇编指令。因此,如果您正在编写一些依赖于平台的代码,最好使用内部函数来提高速度:

        #define htonl(x) _byteswap_ulong(x)
        

        这对于 .PNG 图像处理可能特别重要,其中所有整数都保存在 Big Endian 中,并说明“可以使用 htonl()...”{以减慢典型的 Windows 程序,如果你没有准备好}。

        【讨论】:

        • Demo: godbolt.org/z/G79hrEPba 在这里您可以看到 gcc 和 clang 将 htonl 内联到单个 bswap 指令,而 msvc 调用一个函数。
        【解决方案13】:

        大多数平台都有一个提供高效字节交换功能的系统头文件。在 Linux 上,它位于 &lt;endian.h&gt;。你可以用 C++ 很好地包装它:

        #include <iostream>
        
        #include <endian.h>
        
        template<size_t N> struct SizeT {};
        
        #define BYTESWAPS(bits) \
        template<class T> inline T htobe(T t, SizeT<bits / 8>) { return htobe ## bits(t); } \
        template<class T> inline T htole(T t, SizeT<bits / 8>) { return htole ## bits(t); } \
        template<class T> inline T betoh(T t, SizeT<bits / 8>) { return be ## bits ## toh(t); } \
        template<class T> inline T letoh(T t, SizeT<bits / 8>) { return le ## bits ## toh(t); }
        
        BYTESWAPS(16)
        BYTESWAPS(32)
        BYTESWAPS(64)
        
        #undef BYTESWAPS
        
        template<class T> inline T htobe(T t) { return htobe(t, SizeT<sizeof t>()); }
        template<class T> inline T htole(T t) { return htole(t, SizeT<sizeof t>()); }
        template<class T> inline T betoh(T t) { return betoh(t, SizeT<sizeof t>()); }
        template<class T> inline T letoh(T t) { return letoh(t, SizeT<sizeof t>()); }
        
        int main()
        {
            std::cout << std::hex;
            std::cout << htobe(static_cast<unsigned short>(0xfeca)) << '\n';
            std::cout << htobe(0xafbeadde) << '\n';
        
            // Use ULL suffix to specify integer constant as unsigned long long 
            std::cout << htobe(0xfecaefbeafdeedfeULL) << '\n';
        }
        

        输出:

        cafe
        deadbeaf
        feeddeafbeefcafe
        

        【讨论】:

        • 更改:#define BYTESWAPS(bits) \ template inline T htobe(T t, SizeT) { return htobe ## bits(t); } \ template inline T htole(T t, SizeT) { return htole ## bits(t); } \ template inline T betoh(T t, SizeT) { return be ## bits ## toh(t); } \ template inline T letoh(T t, SizeT) { return le ## bits ## toh(t); }
        • 谢谢,忘记测试 betoh() 和 letoh()。
        【解决方案14】:

        说真的……我不明白为什么所有的解决方案都那么复杂最简单、最通用的模板函数如何在任何操作系统中的任何情况下交换任何类型的任何大小??

        template <typename T>
        void SwapEnd(T& var)
        {
            static_assert(std::is_pod<T>::value, "Type must be POD type for safety");
            std::array<char, sizeof(T)> varArray;
            std::memcpy(varArray.data(), &var, sizeof(T));
            for(int i = 0; i < static_cast<int>(sizeof(var)/2); i++)
                std::swap(varArray[sizeof(var) - 1 - i],varArray[i]);
            std::memcpy(&var, varArray.data(), sizeof(T));
        }
        

        这是 C 和 C++ 结合的神奇力量!只需逐个字符交换原始变量。

        第 1 点:无运算符:请记住,我没有使用简单的赋值运算符“=”,因为当字节序翻转和复制构造函数(或赋值运算符) 不会工作。因此,一个字符一个字符地复制它们更可靠。

        第 2 点:注意对齐问题:请注意,我们正在与数组进行复制,这是正确的做法,因为 C++ 编译器不保证我们可以访问未对齐的内存(此答案已从其原始形式更新)。例如,如果你分配uint64_t,你的编译器不能保证你可以访问它的第三个字节作为uint8_t。因此,正确的做法是将其复制到一个 char 数组,交换它,然后将其复制回来(所以没有reinterpret_cast)。请注意,如果编译器能够访问单个字节而不考虑对齐方式,那么它们大多足够聪明,可以将您所做的转换回reinterpret_cast

        要使用此功能

        double x = 5;
        SwapEnd(x);
        

        现在x 的字节序有所不同。

        【讨论】:

        • 这在任何地方都可以使用,但是生成的汇编 ocde 通常不是最理想的:请参阅我的问题 stackoverflow.com/questions/36657895/…
        • 你使用new/delete为此分配缓冲区?!? sizeof(var) 是一个编译时常量,所以你可以使用char varSwapped[sizeof(var)]。或者你可以做char *p = reinterpret_cast&lt;char*&gt;(&amp;var) 并就地交换。
        • @Peter 这个答案是快速而肮脏的,以证明一个观点。我会落实你的建议。但是,与那里放弃的 50 行解决方案相比,您不必成为超级 SO AH 并否决 5 行解决方案。我就不多说了。
        • 这个答案提出了一些有用的观点,即在错误的字节序数据上小心构造函数和重载运算符,所以一旦代码不可怕,我很乐意删除我的反对意见,并且是一个好的编译器可以编译成 bswap 指令。另外,我建议使用for(size_t i = 0 ; i &lt; sizeof(var) ; i++) 而不是static_cast&lt;long&gt;。 (或者实际上,就地交换将使用升序和降序 char* 无论如何都会消失)。
        • 例如请参阅Mark Ransom's answer 使用 std::swap 就地反转。
        【解决方案15】:

        我有这段代码可以让我从 HOST_ENDIAN_ORDER(不管它是什么)转换为 LITTLE_ENDIAN_ORDER 或 BIG_ENDIAN_ORDER。我使用模板,所以如果我尝试从 HOST_ENDIAN_ORDER 转换为 LITTLE_ENDIAN_ORDER 并且它们恰好与我编译的机器相同,则不会生成任何代码。

        这里是一些 cmets 的代码:

        // We define some constant for little, big and host endianess. Here I use 
        // BOOST_LITTLE_ENDIAN/BOOST_BIG_ENDIAN to check the host indianess. If you
        // don't want to use boost you will have to modify this part a bit.
        enum EEndian
        {
          LITTLE_ENDIAN_ORDER,
          BIG_ENDIAN_ORDER,
        #if defined(BOOST_LITTLE_ENDIAN)
          HOST_ENDIAN_ORDER = LITTLE_ENDIAN_ORDER
        #elif defined(BOOST_BIG_ENDIAN)
          HOST_ENDIAN_ORDER = BIG_ENDIAN_ORDER
        #else
        #error "Impossible de determiner l'indianness du systeme cible."
        #endif
        };
        
        // this function swap the bytes of values given it's size as a template
        // parameter (could sizeof be used?).
        template <class T, unsigned int size>
        inline T SwapBytes(T value)
        {
          union
          {
             T value;
             char bytes[size];
          } in, out;
        
          in.value = value;
        
          for (unsigned int i = 0; i < size / 2; ++i)
          {
             out.bytes[i] = in.bytes[size - 1 - i];
             out.bytes[size - 1 - i] = in.bytes[i];
          }
        
          return out.value;
        }
        
        // Here is the function you will use. Again there is two compile-time assertion
        // that use the boost librarie. You could probably comment them out, but if you
        // do be cautious not to use this function for anything else than integers
        // types. This function need to be calles like this :
        //
        //     int x = someValue;
        //     int i = EndianSwapBytes<HOST_ENDIAN_ORDER, BIG_ENDIAN_ORDER>(x);
        //
        template<EEndian from, EEndian to, class T>
        inline T EndianSwapBytes(T value)
        {
          // A : La donnée à swapper à une taille de 2, 4 ou 8 octets
          BOOST_STATIC_ASSERT(sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8);
        
          // A : La donnée à swapper est d'un type arithmetic
          BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value);
        
          // Si from et to sont du même type on ne swap pas.
          if (from == to)
             return value;
        
          return SwapBytes<T, sizeof(T)>(value);
        }
        

        【讨论】:

          【解决方案16】:

          我喜欢这个,只是为了风格:-)

          long swap(long i) {
              char *c = (char *) &i;
              return * (long *) (char[]) {c[3], c[2], c[1], c[0] };
          }
          

          【讨论】:

          • 我在char[] 上收到一条错误消息,提示“错误:不允许不完整的类型”
          【解决方案17】:

          如果一个大端序的 32 位无符号整数看起来像等于 2864434397 的 0xAABBCCDD,那么在同样等于 2864434397 的小端序处理器上,同样的 32 位无符号整数看起来像 0xDDCCBBAA。

          如果一个 big-endian 16 位 unsigned short 看起来像等于 43707 的 0xAABB,那么同样的 16 位 unsigned short 在一个也等于 43707 的 little-endian 处理器上看起来像 0xBBAA。

          这里有几个方便的#define 函数可以将字节从小端交换到大端,反之亦然 -->

          // can be used for short, unsigned short, word, unsigned word (2-byte types)
          #define BYTESWAP16(n) (((n&0xFF00)>>8)|((n&0x00FF)<<8))
          
          // can be used for int or unsigned int or float (4-byte types)
          #define BYTESWAP32(n) ((BYTESWAP16((n&0xFFFF0000)>>16))|((BYTESWAP16(n&0x0000FFFF))<<16))
          
          // can be used for unsigned long long or double (8-byte types)
          #define BYTESWAP64(n) ((BYTESWAP32((n&0xFFFFFFFF00000000)>>32))|((BYTESWAP32(n&0x00000000FFFFFFFF))<<32))
          

          【讨论】:

            【解决方案18】:

            只是想我在这里添加了自己的解决方案,因为我在任何地方都没有看到它。它是一个小型且可移植的 C++ 模板化函数,可移植,只使用位操作。

            template<typename T> inline static T swapByteOrder(const T& val) {
                int totalBytes = sizeof(val);
                T swapped = (T) 0;
                for (int i = 0; i < totalBytes; ++i) {
                    swapped |= (val >> (8*(totalBytes-i-1)) & 0xFF) << (8*i);
                }
                return swapped;
            }
            

            【讨论】:

              【解决方案19】:

              这是我想到的一个通用版本,用于交换适当的值。如果性能有问题,其他建议会更好。

               template<typename T>
                  void ByteSwap(T * p)
                  {
                      for (int i = 0;  i < sizeof(T)/2;  ++i)
                          std::swap(((char *)p)[i], ((char *)p)[sizeof(T)-1-i]);
                  }
              

              免责声明:我还没有尝试编译或测试它。

              【讨论】:

                【解决方案20】:

                如果您采用通用模式来反转单词中的位顺序,并剔除反转每个字节中位的部分,那么您将得到仅反转单词中字节的部分。对于 64 位:

                x = ((x & 0x00000000ffffffff) << 32) ^ ((x >> 32) & 0x00000000ffffffff);
                x = ((x & 0x0000ffff0000ffff) << 16) ^ ((x >> 16) & 0x0000ffff0000ffff);
                x = ((x & 0x00ff00ff00ff00ff) <<  8) ^ ((x >>  8) & 0x00ff00ff00ff00ff);
                

                编译器应该清除多余的位掩码操作(我将它们保留以突出显示模式),但如果不这样做,您可以这样重写第一行:

                x = ( x                       << 32) ^  (x >> 32);
                

                在大多数架构上,这通常应该简化为一条循环指令(忽略整个操作可能是一条指令)。

                在 RISC 处理器上,大而复杂的常量可能会导致编译器出现问题。不过,您可以从前一个常数中轻松计算出每个常数。像这样:

                uint64_t k = 0x00000000ffffffff; /* compiler should know a trick for this */
                x = ((x & k) << 32) ^ ((x >> 32) & k);
                k ^= k << 16;
                x = ((x & k) << 16) ^ ((x >> 16) & k);
                k ^= k << 8;
                x = ((x & k) <<  8) ^ ((x >>  8) & k);
                

                如果你喜欢,你可以把它写成一个循环。它不会有效率,但只是为了好玩:

                int i = sizeof(x) * CHAR_BIT / 2;
                uintmax_t k = (1 << i) - 1;
                while (i >= 8)
                {
                    x = ((x & k) << i) ^ ((x >> i) & k);
                    i >>= 1;
                    k ^= k << i;
                }
                

                为了完整起见,这里是第一种形式的简化 32 位版本:

                x = ( x               << 16) ^  (x >> 16);
                x = ((x & 0x00ff00ff) <<  8) ^ ((x >>  8) & 0x00ff00ff);
                

                【讨论】:

                  【解决方案21】:

                  我真的很惊讶没有人提到 htobeXX 和 betohXX 函数。它们在 endian.h 中定义,与网络函数 htonXX 非常相似。

                  【讨论】:

                    【解决方案22】:

                    使用下面的代码,您可以轻松地在 BigEndian 和 LittleEndian 之间进行切换

                    #define uint32_t unsigned 
                    #define uint16_t unsigned short
                    
                    #define swap16(x) ((((uint16_t)(x) & 0x00ff)<<8)| \
                    (((uint16_t)(x) & 0xff00)>>8))
                    
                    #define swap32(x) ((((uint32_t)(x) & 0x000000ff)<<24)| \
                    (((uint32_t)(x) & 0x0000ff00)<<8)| \
                    (((uint32_t)(x) & 0x00ff0000)>>8)| \
                    (((uint32_t)(x) & 0xff000000)>>24))
                    

                    【讨论】:

                      【解决方案23】:

                      哇,我简直不敢相信我在这里读到的一些答案。实际上,汇编中有一条指令比其他任何指令都快。交换。你可以简单地写一个这样的函数......

                      __declspec(naked) uint32_t EndianSwap(uint32 value)
                      {
                          __asm
                          {
                              mov eax, dword ptr[esp + 4]
                              bswap eax
                              ret
                          }
                      }
                      

                      它比建议的内在函数快很多。我把它们拆开看了看。上述函数没有序言/尾声,因此几乎没有任何开销。

                      unsigned long _byteswap_ulong(unsigned long value);
                      

                      做 16 位同样容易,除了你会使用 xchg al,啊。 bswap 仅适用于 32 位寄存器。

                      64 位有点棘手,但也不过分。比上面所有带有循环和模板等的例子都要好。

                      这里有一些注意事项...首先 bswap 仅适用于 80x486 及以上的 CPU。有人打算在 386 上运行它吗?!?如果是这样,您仍然可以将 bswap 替换为...

                      mov ebx, eax
                      shr ebx, 16
                      xchg al, ah
                      xchg bl, bh
                      shl eax, 16
                      or eax, ebx
                      

                      此外,内联汇编仅在 Visual Studio 的 x86 代码中可用。裸函数不能被衬里,并且在 x64 版本中也不可用。在那个实例中,您将不得不使用编译器内在函数。

                      【讨论】:

                      • _byteswap_ulong_uint64(例如在接受的答案中)都编译为使用 bswap 指令。我会很惊讶,但很想知道这个 asm 是否快得多,因为它只省略了序言/尾声——你对它进行了基准测试吗?
                      • @stdcall 这个问题没有要求便携式解决方案,甚至没有提到任何关于平台的内容。正如我的回答所说,以上是关于字节序交换的最快方法。当然,如果您在非 X86 平台上编写此代码,那么这将无法正常工作,但正如我也提到的,如果您的编译器甚至支持它们,那么您将受限于编译器内在函数。
                      • @ZachB 在这种特殊情况下,我认为省略序言和尾声会给您带来不错的节省,因为您实际上只执行了 1 条指令。序言将不得不压入堆栈,进行减法,设置基指针,然后在最后进行类似的操作。我没有对它进行基准测试,但是上面有一个 0 依赖链,如果没有它是赤裸裸的,你根本不会得到它。也许一个好的编译器会内联它,但你在另一个球场。
                      • 也许吧。但请注意,在交换数字数组的常见情况下,其他答案中讨论的编译器内在函数将使用 SSE/AVX 扩展并发出 PSHUFB,其性能优于 BSWAP。见wm.ite.pl/articles/reverse-array-of-bytes.html
                      • 当 OP 没有指定他们只需要 x86 的解决方案时,发布特定于平台的解决方案是错误的恕我直言。并且贬低其他解决方案,当您的解决方案在许多非常广泛使用的操作系统上无法使用时,例如 iOS 和 Android(使用 ARM 或 MIPS CPU)。
                      【解决方案24】:

                      我最近在 C 中编写了一个宏来执行此操作,但它在 C++ 中同样有效:

                      #define REVERSE_BYTES(...) do for(size_t REVERSE_BYTES=0; REVERSE_BYTES<sizeof(__VA_ARGS__)>>1; ++REVERSE_BYTES)\
                          ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES],\
                          ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES],\
                          ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES];\
                      while(0)
                      

                      它接受任何类型并反转传递参数中的字节。 示例用法:

                      int main(){
                          unsigned long long x = 0xABCDEF0123456789;
                          printf("Before: %llX\n",x);
                          REVERSE_BYTES(x);
                          printf("After : %llX\n",x);
                      
                          char c[7]="nametag";
                          printf("Before: %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
                          REVERSE_BYTES(c);
                          printf("After : %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
                      }
                      

                      哪些打印:

                      Before: ABCDEF0123456789
                      After : 8967452301EFCDAB
                      Before: nametag
                      After : gateman
                      

                      以上内容完全可以复制/粘贴,但这里有很多内容,所以我将逐个分解它的工作原理:

                      首先值得注意的是整个宏被封装在一个do while(0) 块中。这是一个common idiom,允许在宏之后使用正常的分号。

                      接下来是使用名为REVERSE_BYTES 的变量作为for 循环的计数器。宏本身的名称用作变量名称,以确保它不会与使用宏的范围内可能存在的任何其他符号冲突。由于该名称是在宏的扩展中使用的,所以在这里用作变量名时不会再次扩展。

                      for 循环中,引用了两个字节和XOR swapped(因此不需要临时变量名):

                      ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES]
                      ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES]
                      

                      __VA_ARGS__ 表示赋予宏的任何内容,用于增加可能传入的内容的灵活性(尽管幅度不大)。然后将该参数的地址转换为unsigned char 指针,以允许通过数组[] 下标交换其字节。

                      最后一个特殊点是缺少{} 大括号。它们不是必需的,因为每个交换中的所有步骤都与comma operator 结合在一起,使它们成为一个语句。

                      最后,值得注意的是,如果速度是重中之重,这并不是理想的方法。如果这是一个重要因素,则其他答案中引用的某些特定于类型的宏或特定于平台的指令可能是更好的选择。但是,这种方法可以移植到所有类型、所有主要平台以及 C 和 C++ 语言。

                      【讨论】:

                      • 在某些代码中的某处发现了这一点。把我弄糊涂了。感谢您的解释。但是为什么要使用__VA_ARGS__
                      【解决方案25】:

                      如果您有 C++ 17,则添加此标头

                      #include <algorithm>
                      

                      使用此模板函数交换字节:

                      template <typename T>
                      void swapEndian(T& buffer)
                      {
                          static_assert(std::is_pod<T>::value, "swapEndian support POD type only");
                          char* startIndex = static_cast<char*>((void*)buffer.data());
                          char* endIndex = startIndex + sizeof(buffer);
                          std::reverse(startIndex, endIndex);
                      }
                      

                      这样称呼它:

                      swapEndian (stlContainer);
                      

                      【讨论】:

                        【解决方案26】:

                        用于实现优化器友好的未对齐非就地字节序访问器的便携式技术。它们适用于每个编译器、每个边界对齐和每个字节排序。根据本机字节序和对齐方式,对这些未对齐的例程进行补充或讨论。部分上市,但你明白了。 BO* 是基于原生字节顺序的常量值。

                        uint32_t sw_get_uint32_1234(pu32)
                        uint32_1234 *pu32;
                        {
                          union {
                            uint32_1234 u32_1234;
                            uint32_t u32;
                          } bou32;
                          bou32.u32_1234[0] = (*pu32)[BO32_0];
                          bou32.u32_1234[1] = (*pu32)[BO32_1];
                          bou32.u32_1234[2] = (*pu32)[BO32_2];
                          bou32.u32_1234[3] = (*pu32)[BO32_3];
                          return(bou32.u32);
                        }
                        
                        void sw_set_uint32_1234(pu32, u32)
                        uint32_1234 *pu32;
                        uint32_t u32;
                        {
                          union {
                            uint32_1234 u32_1234;
                            uint32_t u32;
                          } bou32;
                          bou32.u32 = u32;
                          (*pu32)[BO32_0] = bou32.u32_1234[0];
                          (*pu32)[BO32_1] = bou32.u32_1234[1];
                          (*pu32)[BO32_2] = bou32.u32_1234[2];
                          (*pu32)[BO32_3] = bou32.u32_1234[3];
                        }
                        
                        #if HAS_SW_INT64
                        int64 sw_get_int64_12345678(pi64)
                        int64_12345678 *pi64;
                        {
                          union {
                            int64_12345678 i64_12345678;
                            int64 i64;
                          } boi64;
                          boi64.i64_12345678[0] = (*pi64)[BO64_0];
                          boi64.i64_12345678[1] = (*pi64)[BO64_1];
                          boi64.i64_12345678[2] = (*pi64)[BO64_2];
                          boi64.i64_12345678[3] = (*pi64)[BO64_3];
                          boi64.i64_12345678[4] = (*pi64)[BO64_4];
                          boi64.i64_12345678[5] = (*pi64)[BO64_5];
                          boi64.i64_12345678[6] = (*pi64)[BO64_6];
                          boi64.i64_12345678[7] = (*pi64)[BO64_7];
                          return(boi64.i64);
                        }
                        #endif
                        
                        int32_t sw_get_int32_3412(pi32)
                        int32_3412 *pi32;
                        {
                          union {
                            int32_3412 i32_3412;
                            int32_t i32;
                          } boi32;
                          boi32.i32_3412[2] = (*pi32)[BO32_0];
                          boi32.i32_3412[3] = (*pi32)[BO32_1];
                          boi32.i32_3412[0] = (*pi32)[BO32_2];
                          boi32.i32_3412[1] = (*pi32)[BO32_3];
                          return(boi32.i32);
                        }
                        
                        void sw_set_int32_3412(pi32, i32)
                        int32_3412 *pi32;
                        int32_t i32;
                        {
                          union {
                            int32_3412 i32_3412;
                            int32_t i32;
                          } boi32;
                          boi32.i32 = i32;
                          (*pi32)[BO32_0] = boi32.i32_3412[2];
                          (*pi32)[BO32_1] = boi32.i32_3412[3];
                          (*pi32)[BO32_2] = boi32.i32_3412[0];
                          (*pi32)[BO32_3] = boi32.i32_3412[1];
                        }
                        
                        uint32_t sw_get_uint32_3412(pu32)
                        uint32_3412 *pu32;
                        {
                          union {
                            uint32_3412 u32_3412;
                            uint32_t u32;
                          } bou32;
                          bou32.u32_3412[2] = (*pu32)[BO32_0];
                          bou32.u32_3412[3] = (*pu32)[BO32_1];
                          bou32.u32_3412[0] = (*pu32)[BO32_2];
                          bou32.u32_3412[1] = (*pu32)[BO32_3];
                          return(bou32.u32);
                        }
                        
                        void sw_set_uint32_3412(pu32, u32)
                        uint32_3412 *pu32;
                        uint32_t u32;
                        {
                          union {
                            uint32_3412 u32_3412;
                            uint32_t u32;
                          } bou32;
                          bou32.u32 = u32;
                          (*pu32)[BO32_0] = bou32.u32_3412[2];
                          (*pu32)[BO32_1] = bou32.u32_3412[3];
                          (*pu32)[BO32_2] = bou32.u32_3412[0];
                          (*pu32)[BO32_3] = bou32.u32_3412[1];
                        }
                        
                        float sw_get_float_1234(pf)
                        float_1234 *pf;
                        {
                          union {
                            float_1234 f_1234;
                            float f;
                          } bof;
                          bof.f_1234[0] = (*pf)[BO32_0];
                          bof.f_1234[1] = (*pf)[BO32_1];
                          bof.f_1234[2] = (*pf)[BO32_2];
                          bof.f_1234[3] = (*pf)[BO32_3];
                          return(bof.f);
                        }
                        
                        void sw_set_float_1234(pf, f)
                        float_1234 *pf;
                        float f;
                        {
                          union {
                            float_1234 f_1234;
                            float f;
                          } bof;
                          bof.f = (float)f;
                          (*pf)[BO32_0] = bof.f_1234[0];
                          (*pf)[BO32_1] = bof.f_1234[1];
                          (*pf)[BO32_2] = bof.f_1234[2];
                          (*pf)[BO32_3] = bof.f_1234[3];
                        }
                        
                        double sw_get_double_12345678(pd)
                        double_12345678 *pd;
                        {
                          union {
                            double_12345678 d_12345678;
                            double d;
                          } bod;
                          bod.d_12345678[0] = (*pd)[BO64_0];
                          bod.d_12345678[1] = (*pd)[BO64_1];
                          bod.d_12345678[2] = (*pd)[BO64_2];
                          bod.d_12345678[3] = (*pd)[BO64_3];
                          bod.d_12345678[4] = (*pd)[BO64_4];
                          bod.d_12345678[5] = (*pd)[BO64_5];
                          bod.d_12345678[6] = (*pd)[BO64_6];
                          bod.d_12345678[7] = (*pd)[BO64_7];
                          return(bod.d);
                        }
                        
                        void sw_set_double_12345678(pd, d)
                        double_12345678 *pd;
                        double d;
                        {
                          union {
                            double_12345678 d_12345678;
                            double d;
                          } bod;
                          bod.d = d;
                          (*pd)[BO64_0] = bod.d_12345678[0];
                          (*pd)[BO64_1] = bod.d_12345678[1];
                          (*pd)[BO64_2] = bod.d_12345678[2];
                          (*pd)[BO64_3] = bod.d_12345678[3];
                          (*pd)[BO64_4] = bod.d_12345678[4];
                          (*pd)[BO64_5] = bod.d_12345678[5];
                          (*pd)[BO64_6] = bod.d_12345678[6];
                          (*pd)[BO64_7] = bod.d_12345678[7];
                        }
                        

                        如果不与访问器一起使用,这些类型定义的好处是会引发编译器错误,从而减少被遗忘的访问器错误。

                        typedef char int8_1[1], uint8_1[1];
                        
                        typedef char int16_12[2], uint16_12[2]; /* little endian */
                        typedef char int16_21[2], uint16_21[2]; /* big endian */
                        
                        typedef char int24_321[3], uint24_321[3]; /* Alpha Micro, PDP-11 */
                        
                        typedef char int32_1234[4], uint32_1234[4]; /* little endian */
                        typedef char int32_3412[4], uint32_3412[4]; /* Alpha Micro, PDP-11 */
                        typedef char int32_4321[4], uint32_4321[4]; /* big endian */
                        
                        typedef char int64_12345678[8], uint64_12345678[8]; /* little endian */
                        typedef char int64_34128756[8], uint64_34128756[8]; /* Alpha Micro, PDP-11 */
                        typedef char int64_87654321[8], uint64_87654321[8]; /* big endian */
                        
                        typedef char float_1234[4]; /* little endian */
                        typedef char float_3412[4]; /* Alpha Micro, PDP-11 */
                        typedef char float_4321[4]; /* big endian */
                        
                        typedef char double_12345678[8]; /* little endian */
                        typedef char double_78563412[8]; /* Alpha Micro? */
                        typedef char double_87654321[8]; /* big endian */
                        

                        【讨论】:

                        • 对于这个问题,C++ 标签有所不同。由于 C++ 和联合,存在许多未定义的行为。
                        【解决方案27】:

                        在模板函数中使用 ye olde 3-step-xor 技巧围绕枢轴进行字节交换提供了一种灵活、快速的 O(ln2) 解决方案,不需要库,这里的样式也拒绝 1 字节类型:

                        template<typename T>void swap(T &t){
                            for(uint8_t pivot = 0; pivot < sizeof(t)/2; pivot ++){
                                *((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
                                *((uint8_t *)&t+sizeof(t)-1- pivot) ^= *((uint8_t *)&t + pivot);
                                *((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
                            }
                        }
                        

                        【讨论】:

                          【解决方案28】:

                          似乎安全的方法是在每个单词上使用 htons。所以,如果你有......

                          std::vector<uint16_t> storage(n);  // where n is the number to be converted
                          
                          // the following would do the trick
                          std::transform(word_storage.cbegin(), word_storage.cend()
                            , word_storage.begin(), [](const uint16_t input)->uint16_t {
                            return htons(input); });
                          

                          如果您在大端系统上,上述内容将是无操作的,因此我会寻找您的平台用作编译时条件的任何内容来确定 htons 是否为无操作。毕竟是 O(n)。在 Mac 上,它会类似于 ...

                          #if (__DARWIN_BYTE_ORDER != __DARWIN_BIG_ENDIAN)
                          std::transform(word_storage.cbegin(), word_storage.cend()
                            , word_storage.begin(), [](const uint16_t input)->uint16_t {
                            return htons(input); });
                          #endif
                          

                          【讨论】:

                            【解决方案29】:

                            这是一个交换小端和大端的基本功能。它是基本的,但不需要补充库。

                            void endianness_swap(uint32_t& val) {
                                uint8_t a, b, c;
                                a = (val & 0xFF000000) >> 24;
                                b = (val & 0x00FF0000) >> 16;
                                c = (val & 0x0000FF00) >> 8;
                                val=(val & 0x000000FF) << 24;
                                val = val + (c << 16) + (b << 8) + (a);
                            }
                            

                            【讨论】:

                              【解决方案30】:
                              void writeLittleEndianToBigEndian(void* ptrLittleEndian, void* ptrBigEndian , size_t bufLen )
                              {
                                  char *pchLittleEndian = (char*)ptrLittleEndian;
                              
                                  char *pchBigEndian = (char*)ptrBigEndian;
                              
                                  for ( size_t i = 0 ; i < bufLen ; i++ )    
                                      pchBigEndian[bufLen-1-i] = pchLittleEndian[i];
                              }
                              
                              std::uint32_t row = 0x12345678;
                              
                              char buf[4]; 
                              
                              writeLittleEndianToBigEndian( &row, &buf, sizeof(row) );
                              

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 2019-02-05
                                • 1970-01-01
                                • 1970-01-01
                                • 2014-03-12
                                • 1970-01-01
                                相关资源
                                最近更新 更多