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

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


注意:我接受的答案直接适用于我的目标编译器(这就是我选择它的原因)。然而,这里还有其他非常好的、更方便的答案。


当前回答

c++20无分支版本,现在std::endian已经存在,但在c++23之前增加了std::byteswap

#include <bit>
#include <type_traits>
#include <concepts>
#include <array>
#include <cstring>
#include <iostream>
#include <bitset>

template <int LEN, int OFF=LEN/2>
class do_swap
{
    // FOR 8 bytes:
    // LEN=8 (LEN/2==4)       <H><G><F><E><D><C><B><A>
    // OFF=4: FROM=0, TO=7 => [A]<G><F><E><D><C><B>[H]
    // OFF=3: FROM=1, TO=6 => [A][B]<F><E><D><C>[G][H]
    // OFF=2: FROM=2, TO=5 => [A][B][C]<E><D>[F][G][H]
    // OFF=1: FROM=3, TO=4 => [A][B][C][D][E][F][G][H]
    // OFF=0: FROM=4, TO=3 => DONE
public:
    enum consts {FROM=LEN/2-OFF, TO=(LEN-1)-FROM};
    using NXT=do_swap<LEN, OFF-1>;
// flip the first and last for the current iteration's range
    static void flip(std::array<std::byte, LEN>& b)
    {
        std::byte tmp=b[FROM];
        b[FROM]=b[TO];
        b[TO]=tmp;
        NXT::flip(b);
    }
};
template <int LEN>
class do_swap<LEN, 0> // STOP the template recursion
{
public:
    static void flip(std::array<std::byte, LEN>&)
    {
    }
};

template<std::integral T, std::endian TO, std::endian FROM=std::endian::native>
        requires ((TO==std::endian::big) || (TO==std::endian::little))
              && ((FROM==std::endian::big) || (FROM==std::endian::little))
class endian_swap
{
public:
    enum consts {BYTE_COUNT=sizeof(T)};
    static T cvt(const T integral)
    {
    // if FROM and TO are the same -- nothing to do
        if (TO==FROM)
        {
                return integral;
        }

    // endian::big --> endian::little is the same as endian::little --> endian::big
    // the bytes have to be reversed
    // memcpy seems to be the most supported way to do byte swaps in a defined way
        std::array<std::byte, BYTE_COUNT> bytes;
        std::memcpy(&bytes, &integral, BYTE_COUNT);
        do_swap<BYTE_COUNT>::flip(bytes);
        T ret;
        std::memcpy(&ret, &bytes, BYTE_COUNT);
        return ret;
    }
};

std::endian big()
{
    return std::endian::big;
}

std::endian little()
{
    return std::endian::little;
}

std::endian native()
{
    return std::endian::native;
}

long long swap_to_big(long long x)
{
    return endian_swap<long long, std::endian::big>::cvt(x);
}

long long swap_to_little(long long x)
{
    return endian_swap<long long, std::endian::little>::cvt(x);
}

void show(std::string label, long long x)
{
    std::cout << label << "\t: " << std::bitset<64>(x) << " (" << x << ")" << std::endl;
}

int main(int argv, char ** argc)
{
    long long init=0xF8FCFEFF7F3F1F0;
    long long to_big=swap_to_big(init);
    long long to_little=swap_to_little(init);
    show("Init", init);
    show(">big", to_big);
    show(">little", to_little);
}

其他回答

如果您采用反转单词中位序的常见模式,并剔除每个字节中反转位的部分,那么您将只剩下反转单词中的字节的部分。对于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);

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

下面介绍如何读取以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;
    }
}

对于这套函数的其余部分,包括写和整数例程,请参阅我的github项目

https://github.com/MalcolmMcLean/ieee754

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

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:没有操作符:请记住,我没有使用简单的赋值操作符“=”,因为当反转字节序时,一些对象将被打乱,复制构造函数(或赋值操作符)将不起作用。因此,一个字符一个字符地复制它们更加可靠。

Point 2: Be aware of alignment issues: Notice that we're copying to and from an array, which is the right thing to do because the C++ compiler doesn't guarantee that we can access unaligned memory (this answer was updated from its original form for this). For example, if you allocate uint64_t, your compiler cannot guarantee that you can access the 3rd byte of that as a uint8_t. Therefore, the right thing to do is to copy this to a char array, swap it, then copy it back (so no reinterpret_cast). Notice that compilers are mostly smart enough to convert what you did back to a reinterpret_cast if they're capable of accessing individual bytes regardless of alignment.

使用此函数:

double x = 5;
SwapEnd(x);

现在x的字节序不同了。

查找位移位,因为这基本上是所有你需要做的交换从小->大端dian。然后根据位的大小,改变位移位的方式。