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


196

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

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

编辑#2:我接受的答案直接适用于我要针对的编译器(这就是为什么我选择了它)。但是,这里还有其他非常好的,更便于移植的答案。


21
ntoh hton可以正常工作,即使它与网络无关。
本·柯林斯

2
通常,处理字节序的最佳方法是确保代码在小字节序和大字节序主机上都可以运行。如果可行,您可能做对了。假设您使用x86 / be是一种危险的做法。
jakobengblom2

10
如果计算机是big-endian,则hton ntoh将无法工作,因为提问者明确要求执行转换。
fabspro 2011年

6
@ jakobengblom2是唯一提及此的人。此页面上的几乎所有示例都使用“交换”字节之类的概念,而不是与底层字节序无关。如果您要处理外部文件格式(具有明确定义的字节序),那么最可移植的操作是将外部数据视为字节流,然后将字节流与原始整数进行相互转换。每当我看到short swap(short x)代码时,我都会感到畏缩,因为如果您转移到具有不同字节序的平台,它将破坏代码。Matthieu M在下面是唯一正确的答案。
马克·拉卡塔

3
您正在考虑的问题完全错误。任务不是“如何在大端值和小端值之间转换”。任务是“如何将特定格式的浮点数和整数值转换为平台的本机格式”。如果操作正确,则本机格式可以是大端,小端,混合端或三进制,适合您的所有代码需求。
David Schwartz 2014年

Answers:


166

如果您使用的是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)

(无需添加任何内容)。Afaik bits.h也以非gcc为中心的方式声明了相同的功能。

16位交换只是一点旋转。

调用内部函数而不是自己动手,可以为您带来最佳的性能和代码密度。


11
对于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起
马特·乔伊纳

20
另外值得一提的是,通过这些内在/总是/交换字节,他们不喜欢htonlhtons等你从你的情况的情况下知道何时真正交换字节。
Brian Vandenberg 2012年

8
@Jason,因为8位数字的大小端字节序相同。:-)
Nils Pipenbrinck

2
@BrianVandenberg正确;在编写可移植代码时,使用htonlntohl不用担心上下文将是可行的,因为定义这些功能的平台会在小端/中端的情况下交换它,而在大端的情况下则是无操作的。但是,在解码定义为小尾数(例如BMP)的标准文件类型时,仍然必须知道上下文并且不能仅仅依赖htonland ntohl
legends2k 2015年

86

简单的说:

#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<uint32_t>(42)


3
进行投票。我只是使用uchars,并分配了4到1、3到2、2到3和1到4,但是如果您使用不同的大小,这将更加灵活。第一代奔腾IIRC上的6个时钟。BSWAP是1个时钟,但特定于平台。

2
@RocketRoy:是的,如果事实证明速度是一个问题,那么使用平台和特定于类型的本征写过载非常简单。
Alexandre C.

3
@MihaiTodor:标准明确允许通过字符数组进行类型转换的联合使用。参见例如。这个问题
Alexandre C.

4
@AlexandreC。不在C ++标准中-仅在C中。在C ++(此代码为)中,此代码是未定义的行为。
拉普兹

4
@Rapptz:3.10似乎很清楚:“如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为是不确定的:[...] char或unsigned char类型。也许我在这里丢失了一些东西,但是对我来说很明显,明确允许通过char指针访问任何类型。
Alexandre C.

75

从Rob Pike 的字节顺序谬论中

假设您的数据流具有一个低位字节编码的32位整数。提取方法如下(假设无符号字节):

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

如果是big-endian,请按以下步骤提取:

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

TL; DR:不用担心您的平台本机顺序,所有计数都是您要读取的流的字节顺序,您最好希望它定义明确。

注意:注释中指出,没有显式类型转换,因此必须dataunsigned charor 的数组uint8_t。使用signed charchar(如果带符号)将data[x]被提升为整数,并data[x] << 24可能将1移入UB的符号位。


5
这很酷,但在我看来,它仅适用于整数和变量。浮动/双打怎么办?
Brett

1
@ v.oddou:是和否,内存映射文件与网络帧完全相同;如果您接受直接阅读它们的话,那么重要的是它们的字节序:如果是little-endian,则使用第一个公式;如果是big-endian,则使用第二个公式。如果字节序匹配,那么任何值得赞扬的编译器都会优化掉不必要的转换。
Matthieu M.

2
@meowsqueak:是的,我希望它能正常工作,因为只有字节顺序改变,而不是每个字节中的位顺序改变。
Matthieu M. 2014年

3
在一个松散相关的注释上,链接的帖子有些令人不愉快...这个家伙似乎很重视简洁,但是他宁愿对那些不好的程序员都写一个长篇大论,而不是像字节序那样开明,而不是实际上解释情况,为什么他的解决方案总是有效的。
广告N

1
如果您使用的是这种方法,请确保将数据转换为(unsigned char *)
joseph

51

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

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解决方案之一将可以正常工作。


2
我相信网络字节顺序是很大的字节序。即使不使用网络代码,也可以牢记这些功能。但是没有ntohf或htonf的浮动版本
Matt

2
马特·H。并非所有计算机系统都具有低字节序字节顺序。如果您正在使用Motorolla 68k,PowerPC或其他大端架构,那么这些功能将根本不会交换字节,因为它们已经处于“网络字节顺序”。
Frosty

2
遗憾的是,htonlntohl不能去小端上的大型平台。
Brian Vandenberg 2012年

2
@celtschk,了解;但是,OP希望有一种切换字节序的方法,即使在大字节序环境中也是如此。
布莱恩·范登堡

4
为了避免不可避免的问题:BE平台需要LE有很多原因;许多文件格式(bmp,fli,pcx,qtm,rtf,tga等)都使用小字节序值…或至少某种格式的格式一次执行一次。
Brian Vandenberg

26

我从这篇文章中提出了一些建议,并将它们组合在一起以形成这样的建议:

#include <boost/type_traits.hpp>
#include <boost/static_assert.hpp>
#include <boost/detail/endian.hpp>
#include <stdexcept>

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

您还必须包含<cstdint>或<stdint.h>,例如,对于uint32_t
ady 2013年

17

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

这是一些示例代码:

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

14
我认为使用逻辑与(&&)而不是按位与(&)是不正确的。根据C ++规范,这两个操作数都隐式转换为bool,这不是您想要的。
特雷弗·罗宾逊

16

有一个称为BSWAP的汇编指令将为您快速完成交换。你可以在这里阅读。

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


这是一个很好的链接。它重新燃起了我对x86汇编程序的兴趣。
PP。

1
BSWAP的时序结果在此处显示。 gmplib.org/~tege/x86-timing.pdf ...以及此处... agner.org/optimize/instruction_tables.pdf

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

8

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


7

您在C语言中所做的相同操作:

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

您也可以声明一个无符号字符的向量,将输入值存入其中,将字节反转为另一个向量,然后将字节存入其中,但这将比位纠缠耗时长几个数量级,尤其是使用64位值时。


7

在大多数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)将有助于从主机顺序转换为网络顺序。


6
这很有趣-opengroup.org/onlinepubs/9699919799/toc.htm上的POSIX标准没有提到标头'<endian.h>`。
乔纳森·莱夫勒

1
htonl无论用例是否与网络有关,您都可以使用和和朋友。网络字节顺序为高位字节序,因此只需将这些功能视为host_to_be和be_to_host。(不过,如果您需要host_to_le则无济于事。)
彼得·科德斯

5

请注意,至少对于Windows,htonl()比其固有的对应文件_byteswap_ulong()慢得多。前者是对ws2_32.dll的DLL库调用,后者是一个BSWAP汇编指令。因此,如果您要编写一些与平台相关的代码,则最好使用内部函数来提高速度:

#define htonl(x) _byteswap_ulong(x)

这对于.PNG图像处理尤其重要,在该过程中,所有整数都保存在Big Endian中并带有解释“一个人可以使用htonl()...” {如果没有准备好,就可以减慢典型的Windows程序}。


4

大多数平台都有一个提供有效字节交换功能的系统头文件。在Linux上,它位于<endian.h>。您可以用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 <class T>内联T htobe(T t,SizeT <bits / 8>){return htobe ## bits(t); } \ template <class T>内联T htole(T t,SizeT <bits / 8>){return htole ## bits(t); } \ template <class T>内联T betoh(T t,SizeT <bits / 8>){return be ## bits ## toh(t); } \ template <class T>内联T letoh(T t,SizeT <bits / 8>){return le ##位## toh(t); }
ldav1s 2011年

谢谢,忘了测试betoh()和letoh()。
Maxim Egorushkin 2011年

4

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

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

我在char[]说“错误:不允许输入不完整的类型”时遇到错误– 2013
Portland Runner

4

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

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,则编译器无法保证您可以将其的第3个字节作为来访问uint8_t。因此,正确的做法是将其复制到char数组,交换它,然后将其复制回(所以不行reinterpret_cast)。请注意,如果编译器reinterpret_cast能够访问单个字节而无需对齐,则它们通常足够聪明,可以将您所做的转换回a 。

要使用此功能

double x = 5;
SwapEnd(x);

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


2
这将在任何地方都有效,但是产生的装配序号通常不是最佳的:请参阅我的问题stackoverflow.com/questions/36657895/…–
j_kubik

您使用new/ delete为此分配缓冲区? sizeof(var)是一个编译时常量,因此您可以这样做char varSwapped[sizeof(var)]。或者,您可以做char *p = reinterpret_cast<char*>(&var)就地交换。
彼得·科德斯

@Peter这个答案是快速而肮脏的,以证明这一点。我会执行您的建议。但是,与那里给出的50行解决方案相比,您不必是大型SO AH,也无需对5行解决方案进行投票。我不想多说了。
量子物理学家

这个答案提出了一些有用的观点,即在错误的字节序数据上谨慎使用构造函数和重载运算符,因此,一旦代码不可怕,我很乐意删除我的下降投票,这是一个好的编译器可以编译成bswap的东西指令。另外,我建议使用for(size_t i = 0 ; i < sizeof(var) ; i++)代替static_cast<long>。(或者实际上,就地交换将使用升序和降序,char*以便无论如何消失)。
彼得·科德斯

例如,使用std :: swap 查看Mark Ransom的答案以就地反转。
彼得·科德斯

3

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

这是带有一些注释的代码:

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

3

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

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

这是几个方便的#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))

2

这是我想出的通用版本,用于就地交换值。如果性能有问题,则其他建议会更好。

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

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


2

如果您采用一种通用的模式来反转单词中的位顺序,并且剔除每个字节中反转位的部分,那么剩下的东西只会反转单词中的字节。对于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);

2

只是以为我在这里添加了自己的解决方案,因为我从未在任何地方看到它。这是一个小型且可移植的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;
}

2

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


2

使用下面的代码,您可以轻松在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))

1

我最近编写了一个宏来在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)块中。这是一个常见的成语允许在宏之后使用普通分号。

接下来是使用命名变量REVERSE_BYTES作为for循环的计数器。宏本身的名称用作变量名,以确保它与使用该宏的任何其他符号都不会冲突。由于名称是在宏的扩展中使用的,因此在此处用作变量名称时,不会再次对其进行扩展。

for循环内,有两个字节被引用和XOR交换(因此不需要临时变量名称):

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

__VA_ARGS__表示提供给宏的任何内容,并用于增加可以传入的内容的灵活性(尽管不多)。然后,获取此参数的地址并将其转换为unsigned char指针,以允许通过数组[]下标交换其字节。

最后一个特殊之处是缺少{}括号。它们不是必需的,因为每次交换中的所有步骤都与逗号运算符连接在一起,从而使它们成为一条语句。

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


在某些代码中的某处找到了它。弄乱了我。感谢您的解释。但是为什么要使用__VA_ARGS__
asr9

0

哇,我简直不敢相信我在这里读到的一些答案。实际上,汇编中有一条指令比其他指令执行得更快。bswap。您可以简单地编写一个像这样的函数...

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

这是MUCH快于已经提出的内部函数。我拆开了它们,看了看。上面的函数没有序言/结尾,因此实际上根本没有开销。

unsigned long _byteswap_ulong(unsigned long value);

除使用xchg al之外,执行16位操作同样简单。bswap仅适用于32位寄存器。

64位有点棘手,但并不是那么棘手。比上面所有带有循环和模板等的示例要好得多。

这里有一些警告...首先,bswap仅在80x486 CPU及更高版本上可用。是否有人打算在386上运行它?!如果是这样,您仍然可以将bswap替换为...

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

此外,内联汇编仅在Visual Studio中的x86代码中可用。裸函数无法内衬,在x64构建中也不可用。在那种情况下,您将不得不使用编译器内部函数。


1
_byteswap_ulong_uint64(例如,在接受的答案中)都可以编译为使用bswap指令。我会感到惊讶,但很想知道这个asm是否快得多,因为它只忽略了序言/结尾-您是否对它进行了基准测试?
ZachB

@stdcall这个问题并没有要求可移植的解决方案,甚至没有提到有关平台的任何内容。如我的回答所述,以上内容是进行字节序交换的最快方法。当然,如果您是在非X86平台上编写此代码,那么它将无法正常工作,但是正如我还提到的,如果您的编译器甚至支持它们,那么您就仅限于编译器内部函数。
焊工

@ZachB在这种特殊情况下,我认为省略序言和结尾将为您节省大量的费用,因为您实际上只执行了一条指令。序言将必须推入堆栈,进行减法,设置基指针,然后在末尾类似。我没有对其进行基准测试,但是上面的代码有一个0依赖项链,如果没有它,那么就根本无法获得。也许一个好的编译器会内联它,但是那时您处于另一个困境。
焊工

2
也许。但是请注意,在交换数字数组的常见情况下,其他答案中讨论的编译器内在函数将使用SSE / AVX扩展并发出PSHUFB,其性能优于BSWAP。参见wm.ite.pl/articles/reverse-array-of-bytes.html
ZachB '16

当OP没有指定他们仅需要针对x86的解决方案时,以IMHO的形式发布平台特定的解决方案是不好的。为了贬低其他解决方案,当您的解决方案无法在许多非常广泛使用的操作系统(例如iOS和Android(使用ARM或MIPS CPU)上无法使用)时
Jens Alfke

0

用于实现对优化程序友好的未对齐非就地字节序访问器的便携式技术。它们适用于每个编译器,每个边界对齐和每个字节顺序。这些未对齐的例程将根据本机字节序和对齐方式进行补充或讨论。部分上市,但您明白了。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的好处是,如果不与访问器一起使用,则会引发编译器错误,从而减轻被遗忘的访问器错误。

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 */

2
对于这个问题,C ++标签有所作为。由于C ++和联合,存在许多不确定的行为。
jww

0

即使您的主机使用其他系统,这也是读取以IEEE 754 64位格式存储的double的方法。

/*
* read a double from a stream in ieee754 format regardless of host
*  encoding.
*  fp - the stream
*  bigendian - set to if big bytes first, clear for little bytes
*              first
*
*/
double freadieee754(FILE *fp, int bigendian)
{
    unsigned char buff[8];
    int i;
    double fnorm = 0.0;
    unsigned char temp;
    int sign;
    int exponent;
    double bitval;
    int maski, mask;
    int expbits = 11;
    int significandbits = 52;
    int shift;
    double answer;

    /* read the data */
    for (i = 0; i < 8; i++)
        buff[i] = fgetc(fp);
    /* just reverse if not big-endian*/
    if (!bigendian)
    {
        for (i = 0; i < 4; i++)
        {
            temp = buff[i];
            buff[i] = buff[8 - i - 1];
            buff[8 - i - 1] = temp;
        }
    }
    sign = buff[0] & 0x80 ? -1 : 1;
    /* exponet in raw format*/
    exponent = ((buff[0] & 0x7F) << 4) | ((buff[1] & 0xF0) >> 4);

    /* read inthe mantissa. Top bit is 0.5, the successive bits half*/
    bitval = 0.5;
    maski = 1;
    mask = 0x08;
    for (i = 0; i < significandbits; i++)
    {
        if (buff[maski] & mask)
            fnorm += bitval;

        bitval /= 2.0;
        mask >>= 1;
        if (mask == 0)
        {
            mask = 0x80;
            maski++;
        }
    }
    /* handle zero specially */
    if (exponent == 0 && fnorm == 0)
        return 0.0;

    shift = exponent - ((1 << (expbits - 1)) - 1); /* exponent = shift + bias */
    /* nans have exp 1024 and non-zero mantissa */
    if (shift == 1024 && fnorm != 0)
        return sqrt(-1.0);
    /*infinity*/
    if (shift == 1024 && fnorm == 0)
    {

#ifdef INFINITY
        return sign == 1 ? INFINITY : -INFINITY;
#endif
        return  (sign * 1.0) / 0.0;
    }
    if (shift > -1023)
    {
        answer = ldexp(fnorm + 1.0, shift);
        return answer * sign;
    }
    else
    {
        /* denormalised numbers */
        if (fnorm == 0.0)
            return 0.0;
        shift = -1022;
        while (fnorm < 1.0)
        {
            fnorm *= 2;
            shift--;
        }
        answer = ldexp(fnorm, shift);
        return answer * sign;
    }
}

对于其余的功能套件,包括write和integer例程,请参阅我的github项目

https://github.com/MalcolmMcLean/ieee754


0

围绕模板功能中的枢轴使用旧的3步异或技巧进行字节交换提供了一种不需要库的灵活,快速的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);
    }
}

0

似乎安全的方法似乎是在每个单词上使用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); });

如果您使用的是big-endian系统,则上述内容将是无操作的,因此,我将寻找您的平台用作编译时条件的所有内容,以确定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

0

如果您有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);

-4

查找位移,因为基本上这是您要从little-> big endian交换的全部操作。然后,根据位大小,更改移位的方式。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.