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

前端 未结 30 2531
难免孤独
难免孤独 2020-11-21 23:18

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

EDIT: For clarity, I have to translate binary data (double-precision floating point values and 3

相关标签:
30条回答
  • 2020-11-21 23:52

    If you're doing this to transfer data between different platforms look at the ntoh and hton functions.

    0 讨论(0)
  • 2020-11-21 23:52

    The same way you do in C:

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

    You could also declare a vector of unsigned chars, memcpy the input value into it, reverse the bytes into another vector and memcpy the bytes out, but that'll take orders of magnitude longer than bit-twiddling, especially with 64-bit values.

    0 讨论(0)
  • 2020-11-21 23:54

    Using the codes below, you can swap between BigEndian and LittleEndian easily

    #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))
    
    0 讨论(0)
  • 2020-11-21 23:54

    I recently wrote a macro to do this in C, but it's equally valid in 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)
    

    It accepts any type and reverses the bytes in the passed argument. Example usages:

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

    Which prints:

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

    The above is perfectly copy/paste-able, but there's a lot going on here, so I'll break down how it works piece by piece:

    The first notable thing is that the entire macro is encased in a do while(0) block. This is a common idiom to allow normal semicolon use after the macro.

    Next up is the use of a variable named REVERSE_BYTES as the for loop's counter. The name of the macro itself is used as a variable name to ensure that it doesn't clash with any other symbols that may be in scope wherever the macro is used. Since the name is being used within the macro's expansion, it won't be expanded again when used as a variable name here.

    Within the for loop, there are two bytes being referenced and XOR swapped (so a temporary variable name is not required):

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

    __VA_ARGS__ represents whatever was given to the macro, and is used to increase the flexibility of what may be passed in (albeit not by much). The address of this argument is then taken and cast to an unsigned char pointer to permit the swapping of its bytes via array [] subscripting.

    The final peculiar point is the lack of {} braces. They aren't necessary because all of the steps in each swap are joined with the comma operator, making them one statement.

    Finally, it's worth noting that this is not the ideal approach if speed is a top priority. If this is an important factor, some of the type-specific macros or platform-specific directives referenced in other answers are likely a better option. This approach, however, is portable to all types, all major platforms, and both the C and C++ languages.

    0 讨论(0)
  • 2020-11-21 23:55

    I took a few suggestions from this post and put them together to form this:

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

    You would then use it as follows:

    // swaps val from host-byte-order to network-byte-order
    auto swapped = byte_swap<host_endian, network_endian>(val);
    

    and vice-versa

    // swap a value received from the network into host-byte-order
    auto val = byte_swap<network_endian, host_endian>(val_from_network);
    
    0 讨论(0)
  • 2020-11-21 23:56

    Simply put:

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

    usage: swap_endian<uint32_t>(42).

    0 讨论(0)
提交回复
热议问题