关于字节序:如何在C ++中的大字节序和小字节序之间转换?

How do I convert between big-endian and little-endian values in C++?

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

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

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


如果您使用的是Visual C ++,请执行以下操作:您包括intrin.h并调用以下函数:

对于16位数字:

1
unsigned short _byteswap_ushort(unsigned short value);

对于32位数字:

1
unsigned long _byteswap_ulong(unsigned long value);

对于64位数字:

1
unsigned __int64 _byteswap_uint64(unsigned __int64 value);

8位数字(字符)无需转换。

同样,这些仅针对无符号值定义,它们也适用于有符号整数。

对于浮点数和双精度数,使用纯整数会更困难,因为它们可能以或不以主机字节顺序出现。您可以在big-endian计算机上获得little-endian浮动,反之亦然。

其他编译器也具有类似的内在函数。

例如,在GCC中,您可以直接调用:

1
2
int32_t __builtin_bswap32 (int32_t x)
int64_t __builtin_bswap64 (int64_t x)

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

16位交换只是一点旋转。

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


简单的说:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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(42)


从Rob Pyke的字节顺序谬论中:

Let's say your data stream has a little-endian-encoded 32-bit integer. Here's how to extract it (assuming unsigned bytes):

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

If it's big-endian, here's how to extract it:

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

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

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


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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#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);
}


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

这是一些示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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(), and _byteswap_int64()。其他平台也应该存在类似的情况,但是我不知道它们会被称为什么。


我们已经使用模板完成了此操作。您可以执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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);
}

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


您在C中所做的相同方式:

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

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


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

1
2
3
4
5
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)将有助于从主机顺序转换为网络顺序。


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

1
#define htonl(x) _byteswap_ulong(x)

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


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

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


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

1
2
3
4
5
6
7
template <typename T>
void SwapEnd(T& var)
{
    char* varArray = reinterpret_cast<char*>(&var);
    for(long i = 0; i < static_cast<long>(sizeof(var)/2); i++)
        std::swap(varArray[sizeof(var) - 1 - i],varArray[i]);
}

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

请记住,我没有使用简单的赋值运算符" =",因为当字节序翻转时某些对象会被弄乱,而复制构造函数(或赋值运算符)将无法工作。因此,逐个字符地复制它们更加可靠。

调用它,只需使用

1
2
double x = 5;
SwapEnd(x);

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


大多数平台都有一个提供有效字节交换功能的系统头文件。在Linux上,它位于中。您可以用C ++很好地包装它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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)) << '
'
;
    std::cout << htobe(0xafbeadde) << '
'
;

    // Use ULL suffix to specify integer constant as unsigned long long
    std::cout << htobe(0xfecaefbeafdeedfeULL) << '
'
;
}

输出:

1
2
3
cafe
deadbeaf
feeddeafbeefcafe


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

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

这是几个方便的#define函数,用于将字节从小端到大端交换,反之亦然->

1
2
3
4
5
6
7
8
// 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))

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 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);
}

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

1
2
3
4
5
6
 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]);
    }

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


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

1
2
3
4
5
6
7
8
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;
}


如果采用通用的模式来反转单词中的位顺序,然后剔除每个字节中的位反转部分,那么剩下的东西只会使单词中的字节反转。对于64位:

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

编译器应清除多余的位屏蔽操作(我将其保留以突出显示模式),但是如果没有,则可以用以下方式重写第一行:

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

通常,在大多数体系结构上,这应该简化为单个轮换指令(忽略整个操作可能是一条指令)。

在RISC处理器上,大而复杂的常量可能会导致编译器遇到困难。不过,您可以从上一个简单地计算每个常数。像这样:

1
2
3
4
5
6
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);

如果愿意,可以将其编写为循环。效率不高,只是为了好玩:

1
2
3
4
5
6
7
8
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位版本:

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

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


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

1
2
3
4
5
#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)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(){
    unsigned long long x = 0xABCDEF0123456789;
    printf("Before: %llX
"
,x);
    REVERSE_BYTES(x);
    printf("After : %llX
"
,x);

    char c[7]="nametag";
    printf("Before: %c%c%c%c%c%c%c
"
,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
"
,c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
}

哪些打印:

1
2
3
4
Before: ABCDEF0123456789
After : 8967452301EFCDAB
Before: nametag
After : gateman

上面的代码完全可以复制/粘贴,但是这里有很多事情要做,所以我将逐个细分它的工作方式:

首先值得注意的是,整个宏都包含在do while(0)块中。这是允许在宏之后使用常规分号的常见习语。

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

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

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

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

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

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


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

1
2
3
4
5
6
7
8
9
10
#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))

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

1
2
3
4
5
6
7
8
9
__declspec(naked) uint32_t EndianSwap(uint32 value)
{
    __asm
    {
        mov eax, dword ptr[esp + 4]
        bswap eax
        ret
    }
}

它比已建议的内在函数快得多。我拆开了它们,看了看。上面的函数没有序言/结尾,因此实际上根本没有开销。

1
unsigned long _byteswap_ulong(unsigned long value);

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

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

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

1
2
3
4
5
6
mov ebx, eax
shr ebx, 16
xchg bl, bh
xchg al, ah
shl eax, 16
or eax, ebx

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


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

1
2
3
4
5
6
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上,它就像...

1
2
3
4
5
#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

尝试Boost::endian,不要自己实施!

这是一个链接


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/*
* 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


围绕模板函数中的枢轴使用旧的三步异或技巧进行字节交换,从而提供了一种灵活,快速的O(ln2)解决方案,不需要库,此处的样式也拒绝1个字节类型:

1
2
3
4
5
6
7
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);
    }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
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的好处是,如果不与访问器一起使用,则会引发编译器错误,从而减轻被遗忘的访问器错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 */


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