Как преобразовать значения с прямым порядком байтов и байтов с обратным порядком байтов в C ++? - PullRequest
179 голосов
/ 20 сентября 2008

Как преобразовать значения с прямым и младшим порядком в C ++?

РЕДАКТИРОВАТЬ: Для ясности мне нужно перевести двоичные данные (значения с плавающей запятой двойной точности и 32-разрядные и 64-разрядные целые числа) из одной архитектуры ЦП в другую. Это не связано с сетью, поэтому ntoh () и подобные функции здесь не будут работать.

РЕДАКТИРОВАТЬ # 2: Ответ, который я принял, относится непосредственно к компиляторам, на которые я нацеливаюсь (именно поэтому я выбрал его). Однако есть и другие очень хорошие, более портативные ответы.

Ответы [ 30 ]

151 голосов
/ 20 сентября 2008

Если вы используете 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-битный своп, это всего лишь бит-поворот.

Вызов встроенных функций вместо того, чтобы использовать свои собственные, дает вам наилучшую производительность и плотность кода между прочим.

76 голосов
/ 10 февраля 2011

Проще говоря:

#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).

68 голосов
/ 27 апреля 2012

От Ошибка порядка байтов от Роба Пайка:

Допустим, ваш поток данных имеет 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: не беспокойтесь о собственном порядке вашей платформы, все, что имеет значение, это порядок байтов потока, из которого вы читаете, и вам лучше надеяться, что он хорошо определен.

Примечание: в комментарии было отмечено, что при отсутствии явного преобразования типов важно, чтобы data был массивом unsigned char или uint8_t. Использование signed char или char (если подписано) приведет к тому, что data[x] будет преобразован в целое число и data[x] << 24 потенциально сместит 1 в знаковый бит, который является UB.

48 голосов
/ 20 сентября 2008

Если вы делаете это в целях совместимости сети / хоста, вы должны использовать:

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 будет работать нормально.

25 голосов
/ 19 августа 2010

Я взял несколько предложений из этого поста и собрал их вместе, чтобы сформировать это:

#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);
}
15 голосов
/ 20 сентября 2008

Существует инструкция по сборке под названием BSWAP, которая сделает замену, очень быстро . Вы можете прочитать об этом здесь .

Visual Studio, или точнее библиотека времени выполнения Visual C ++, имеет встроенную платформу для этого, называемую _byteswap_ushort(), _byteswap_ulong(), and _byteswap_int64(). Подобное должно существовать для других платформ, но я не знаю, как они будут называться.

13 голосов
/ 20 сентября 2008

Процедура перехода от порядкового номера к порядку байтов аналогична переходу от порядкового номера к порядку байтов.

Вот пример кода:

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);
}
12 голосов
/ 20 сентября 2008

Мы сделали это с помощью шаблонов. Вы могли бы так что-то вроде этого:

// 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 голосов
/ 20 сентября 2008

Если вы делаете это для передачи данных между различными платформами, посмотрите на функции ntoh и hton.

6 голосов
/ 20 сентября 2008

В большинстве систем 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) будут полезны для преобразования порядка хостов в порядок сетей.

...