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

时间:2020-03-06 14:27:41  来源:igfitidea点击:

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

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

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

解决方案

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

我们已经使用模板完成了此操作。我们可以这样做:

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

我们在C中所做的相同方式:

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

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

查找位移,因为这基本上是我们要从little-> big endian进行交换所要做的全部。然后,根据位大小,更改位移位的方式。

如果我们使用的是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中,我们可以直接调用:

int32_t __builtin_bswap32 (int32_t x)
int64_t __builtin_bswap64 (int64_t x)

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

16位交换只是一点旋转。

调用内在函数而不是自己动手,将为我们带来最佳的性能和代码密度。

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

这是一些示例代码:

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

在大多数POSIX系统上(不是POSIX标准中的),都有endian.h,可用于确定系统使用哪种编码。从那里是这样的:

unsigned int change_endian(unsinged 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)将有助于从主机顺序转换为网络顺序。

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

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

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

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

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

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

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

我有这段代码,可以让我从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);
}

我从这篇文章中提出了一些建议,并将它们汇总在一起形成了以下内容:

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