Как мне преобразовать между прямым порядком байтов и прямым порядком байтов в C++?

Как мне преобразовать между прямым порядком и обратным порядком байтов в C++?

Для ясности мне нужно преобразовать двоичные данные (значения с плавающей запятой двойной точности и 32-битные и 64-битные целые числа) из одной архитектуры процессора в другую. Это не связано с сетью, поэтому ntoh () и аналогичные функции здесь не будут работать.


Примечание. Ответ, который я принял, применяется непосредственно к компиляторам, на которые я нацелен (поэтому я выбрал его). Однако здесь есть и другие очень хорошие, более переносимые ответы.

Вам нужно преобразовать между прямым порядком байтов и прямым порядком байтов или между одним из них и вашим собственным форматом для другой обработки?

wnoise 20.09.2008 00:27

Было бы полезно включить платформу, о которой вы говорите.

terminus 20.09.2008 00:39

ntoh hton будет работать нормально, даже если он не имеет ничего общего с сетью.

Ben Collins 20.09.2008 08:31

Лучший способ справиться с порядком байтов в целом - убедиться, что код работает как на хост-машинах с прямым порядком, так и с прямым порядком байтов. Если это сработает, вероятно, вы все сделали правильно. Предполагать, что вы используете x86 / be, на практике опасно.

jakobengblom2 07.10.2008 00:19

hilton ntoh не будет работать, если машина с прямым порядком байтов, потому что задающий вопрос явно хочет выполнить преобразование.

fabspro 17.11.2011 12:38

@ jakobengblom2 - единственный, кто об этом упомянул. Почти во всех примерах на этой странице используются такие понятия, как «перестановка байтов», вместо того, чтобы делать это независимо от лежащей в основе порядка байтов. Если вы имеете дело с внешними форматами файлов (которые имеют четко определенную последовательность байтов), то наиболее переносимым является обработка внешних данных как поток байтов и преобразование потока байтов в собственные целые числа и обратно. Я съеживаюсь каждый раз, когда вижу код short swap(short x), поскольку он сломается, если вы перейдете на платформу с другим порядком байтов. У Матье М есть единственный правильный ответ ниже.

Mark Lakata 08.03.2013 22:16

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

David Schwartz 07.08.2014 12:20

htons - это короткое соединение между хостом и сетью.

roottraveller 11.07.2017 09:36
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
214
8
324 433
31
Перейти к ответу Данный вопрос помечен как решенный

Ответы 31

Если вы делаете это для передачи данных между разными платформами, посмотрите на функции 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-битными значениями.

Посмотрите на сдвиг бит, так как это в основном все, что вам нужно сделать, чтобы поменять местами с маленького -> большого порядка байтов. Затем, в зависимости от размера бит, вы меняете способ сдвига бит.

Ответ принят как подходящий

Если вы используете 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 вы можете напрямую вызвать некоторые встроенные функции, как описано здесь:

uint32_t __builtin_bswap32 (uint32_t x)
uint64_t __builtin_bswap64 (uint64_t x)

(не нужно что-то включать). Afaik bits.h также объявляет ту же функцию без использования gcc.

16-битный своп - это просто битовый поворот.

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

Вы знаете, доступны ли встроенные функции gcc и на других платформах? т.е. привязаны ли они к хосту x86 или будут работать на PPC, SPARC и т. д.?

jakobengblom2 07.10.2008 00:20

Они должны работать на всех платформах, поддерживаемых gcc. Если целевой ЦП не поддерживает обмен байтами как отдельную инструкцию, компилятор либо встроит оптимизированный код, либо вызовет функцию времени выполнения.

Nils Pipenbrinck 07.10.2008 11:08

С GCC я мог бы использовать: #include <byteswap.h> int32_t bswap_32 (int32_t x) int64_t bswap_64 (int64_t x)

jmanning2k 14.11.2008 19:16

__builtin_bswapX доступен только начиная с GCC-4.3.

Matt Joiner 17.12.2009 07:18

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

Brian Vandenberg 25.04.2012 20:04

Подождите, а как насчет 8-битных чисел? Почему для них нет встроенных функций?

Jason 21.02.2013 17:30

@Jason, потому что 8-битные числа одинаковы в большем и малом порядке байтов. :-)

Nils Pipenbrinck 23.02.2013 13:04

@BrianVandenberg Верно; использование htonl и ntohl, не беспокоясь о контексте, будет работать при написании переносимого кода, поскольку платформа, определяющая эти функции, поменяла бы его местами, если бы он был маленьким / средним порядком байтов, а при обратном порядке байтов это было бы некорректно. Однако при декодировании стандартного типа файла, который определяется как little-endian (скажем, BMP), все равно нужно знать контекст и нельзя просто полагаться на htonl и ntohl.

legends2k 06.02.2015 09:41

К вашему сведению, я использую GCC 4.8.1, а также есть файл __builtin_bswap16.

Kaganar 27.02.2015 21:55
Повышение 1,58 now has Endian library.
Vincas Dargis 30.04.2015 13:58

Я предполагаю, что решение GCC не работает для llvm / mac. Есть аналог?

Eric 14.03.2016 17:18

Также стоит проверить эффективную реализацию по скорости hardwarebug.org/2010/01/14/beware-the-builtins

aka.nice 18.03.2016 16:53

@Eric встроенные функции также находятся в clang, см., Например, использование в boost.org/doc/libs/1_60_0/boost/endian/detail/intrinsic.hpp

aka.nice 18.03.2016 16:55

Этот ответ действительно должен кое-что сказать об определении того, используете ли вы хост с прямым порядком байтов или нет. (Windows + MSVC может нацеливаться на xbox360 с прямым порядком байтов, согласно эта попытка portable_endian.h, что я не рекомендую полностью, поскольку он использует ntohl и т. д. Даже в Windows, где это не встроенный вызов Winsock DLL). В любом случае, определение того, когда следует менять местами байты, является другой сложной проблемой в переносимой программе C++, поскольку AFAIK стандарт ISO C++ не определяет макросы для определения порядка байтов хоста. Просто ссылка на хороший SO Q&A об этом было бы хорошо.

Peter Cordes 02.05.2017 09:20

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

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

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

Последняя опубликованная здесь функция неверна и должна быть отредактирована на: void swapByteOrder (unsigned long long & ull) {ull = (ull >> 56) | ... (ull << 56); }

Eric Burnett 20.09.2008 00:58

Я не считаю правильным использовать логическое и (&&) вместо побитового и (&). Согласно спецификации C++, оба операнда неявно преобразуются в bool, а это не то, что вам нужно.

Trevor Robinson 24.06.2009 01:04

В большинстве систем POSIX (хотя он не входит в стандарт POSIX) существует endian.h, который можно использовать для определения того, какую кодировку использует ваша система. Оттуда это примерно так:

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

Забавно - стандарт POSIX в opengroup.org/onlinepubs/9699919799/toc.htm не упоминает заголовок '<endian.h> `.

Jonathan Leffler 12.01.2010 02:36

Вы можете использовать htonl и друзей независимо от того, имеет ли этот вариант использования какое-либо отношение к сети. Сетевой порядок байтов является прямым порядком байтов, поэтому просто рассматривайте эти функции как host_to_be и be_to_host. (Однако это не поможет, если вам нужен host_to_le.)

Peter Cordes 26.04.2017 19:18

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

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

Это отличная ссылка. Это возродило мой интерес к ассемблеру x86.

PP. 17.11.2009 18:53

Здесь представлены результаты по времени для BSWAP. gmplib.org/~tege/x86-timing.pdf ... а здесь ... agner.org/optimize/instruction_tables.pdf

user1899861 02.03.2013 00:48

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

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

я считаю, что сетевой порядок байтов является прямым порядком байтов. Эти функции можно использовать с учетом этого, даже если вы не используете сетевой код. Однако нет версий с плавающей запятой ntohf или htonf.

Matt 13.11.2010 23:33

Мэтт Х.: Это верно только в большинстве случаев. Не все компьютерные системы имеют порядок байтов с прямым порядком байтов. Если вы работали, скажем, с motorolla 68k, PowerPC или другой архитектурой с прямым порядком байтов, эти функции вообще не меняют байты, потому что они уже находятся в сетевом порядке байтов.

Frosty 15.11.2010 17:36

К сожалению, htonl и ntohl не могут перейти на прямой порядок байтов на платформе с прямым порядком байтов.

Brian Vandenberg 25.04.2012 20:10

@BrianVandenberg: Они созданы не для этого. Они созданы для обеспечения единообразия внешнего формата. Я бы сказал, если вы на самом деле не реализуете эти функции, вам обычно не нужно даже заботиться о том, что это за формат на самом деле.

celtschk 21.07.2013 23:48

@Matt: до тех пор, пока вы используете его только для обеспечения согласованного внешнего формата, но вам не нужно заботиться о том, что это за формат на самом деле, вам не нужно иметь в виду, что это за порядок на самом деле.

celtschk 21.07.2013 23:49

@celtschk, понял; однако OP хочет способ переключения порядка байтов даже в среде с прямым порядком байтов.

Brian Vandenberg 22.07.2013 20:32

Чтобы избежать неизбежного вопроса: существует ряд причин, по которым LE для платформы BE; в ряде форматов файлов (bmp, fli, pcx, qtm, rtf, tga и т. д.) используются значения с прямым порядком байтов ... или, по крайней мере, какая-то версия формата все равно использовалась когда-то.

Brian Vandenberg 22.07.2013 20:38

@BrianVandenberg Вау, не видел этого вашего комментария, прежде чем ответить на него :) Проголосовал за оба!

legends2k 06.02.2015 09:47

Гарантирует ли POSIX, что порядок в сети является прямым порядком байтов?

Ciro Santilli TRUMP BAN IS BAD 08.04.2016 23:45

@CiroSantilli 新疆 改造 中心 996ICU 六四 事件 Нет, конвенция пришла откуда-то еще, и вам понадобится знание протокола, с которым вы работаете, чтобы определить, применимо ли это определение «сетевой порядок» (но это будет, например, с сокетами Беркли параметры)

Lightness Races in Orbit 18.06.2019 18:28

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

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

Затем вы должны использовать его следующим образом:

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

наоборот

// swap a value received from the network into host-byte-order
auto val = byte_swap<network_endian, host_endian>(val_from_network);

вам также необходимо включить <cstdint> или <stdint.h>, например, для uint32_t

ady 24.08.2015 04:12

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

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

Проголосуйте. Я просто использовал uchars и назначил 4 на 1, от 3 до 2, от 2 до 3 и от 1 до 4, но это более гибко, если у вас разные размеры. 6 тактов на Pentium IIRC 1-го поколения. BSWAP составляет 1 такт, но зависит от платформы.

user1899861 01.03.2013 23:53

@RocketRoy: Да, и если скорость окажется проблемой, очень просто написать перегрузку с интригами, зависящими от платформы и типа.

Alexandre C. 14.07.2013 15:23

Это должно быть самое творческое использование союза, которое я когда-либо видел. Спасибо за предоставленный код!

Mihai Todor 04.05.2014 16:23

@MihaiTodor: это использование объединений для приведения типов через массив символов явно разрешено стандартом. См. Например. этот вопрос.

Alexandre C. 04.05.2014 16:36

@AlexandreC. Не в стандарте C++ - только в C. В C++ (которым является этот код) этот код имеет неопределенное поведение.

Rapptz 23.07.2014 02:54

@Rapptz: 3.10 кажется ясным: «Если программа пытается получить доступ к сохраненному значению объекта через glvalue, отличный от одного из следующих типов, поведение не определено: [...] тип char или unsigned char.». Возможно, мне что-то здесь не хватает, но мне было довольно ясно, что доступ к любому типу через указатели char явно разрешен.

Alexandre C. 23.07.2014 22:20

Я считаю, что это слишком сложное решение простой проблемы. На самом деле, я готов поспорить, что большинству программистов (включая меня) потребуется некоторое время, чтобы выяснить, что делает эта функция (и определить, правильный ли код).

Frerich Raabe 06.01.2015 13:10

@FrerichRaabe: В C++ не так много способов получить доступ к отдельным байтам переменной. Я согласен с тем, что правила набора текста довольно сложны, но я не думаю, что это может быть проще, чем код, который я написал, за исключением, возможно, если вы ограничитесь типами с фиксированным размером известен (которые int, long, float и т. д. Не являются ).

Alexandre C. 06.01.2015 22:50

Это неопределенное поведение. 9.5.1: «В объединении в любое время может быть активным не более одного из нестатических элементов данных».

ThomasMcLeod 07.11.2017 20:14

Сделайте эту функцию constexpr, пожалуйста.

einpoklum 29.11.2017 17:35

@AlexandreC. Эта функция может быть освобождена от UB за 1 минуту, просто интерпретируя все как массив uint8_t или byte.

ceztko 16.11.2019 17:34

Значит, вместо returning dest.u;, returning *reinterpret_cast<T*>(dest.u8) будет определять поведение с момента обращения к последнему записанному члену?

oarfish 28.09.2020 18:42

Стандартные отрывки, цитируемые здесь (stackoverflow.com/a/7005988/2397253), похоже, говорят, что это нормально, если вы используете псевдоним через тип char (который, я думаю, подпадает под unsigned char[]). Я ошибаюсь?

oarfish 28.09.2020 19:19

Отвечая на мой собственный вопрос, возможно: было бы нормально получить доступ к dest.u через dest.u8, но не наоборот.

oarfish 28.09.2020 19:20

Большинство платформ имеют файл системного заголовка, который обеспечивает эффективные функции обмена байтами. В Linux это <endian.h>. Вы можете красиво обернуть его на C++:

#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)) << '\n';
    std::cout << htobe(0xafbeadde) << '\n';

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

Выход:

cafe
deadbeaf
feeddeafbeefcafe

Изменение: #define BYTESWAPS (биты) \ template <class T> inline T htobe (T t, SizeT <bits / 8>) {return htobe ## bits (t); } \ template <class T> inlineT 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); }

ldav1s 22.02.2011 23:47

Спасибо, забыл протестировать betoh () и letoh ().

Maxim Egorushkin 23.02.2011 13:16

мне нравится этот, просто для стиля :-)

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

Я получаю сообщение об ошибке на char[]: «Ошибка: неполный тип не допускается»

Automate This 28.10.2013 05:11

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

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

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

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

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.

Это круто, но мне кажется, что это применимо только к целым числам и вариантам. Что делать с поплавками / дублями?

Brett 05.01.2013 09:04

@Brett: строго не знаю :)

Matthieu M. 05.01.2013 17:08

это верно только потому, что ваша переменная i была создана компилятором в стеке. Когда вы используете указатели, которые относятся к сопоставленным данным (или к файлу, который вы полностью вытащили в память), вам также необходимо позаботиться о порядке байтов хоста и выравнивании, что усложняет двойную задачу.

v.oddou 01.11.2013 10:05

@ v.oddou: да и нет, файлы с отображением в память точно такие же, как сетевые фреймы; если вы принимаете нет, чтобы читать их напрямую, все, что имеет значение, это их endianness: если прямой порядок байтов, используйте первую формулу, если прямой порядок байтов, используйте вторую. Любой достойный компилятор оптимизирует ненужные преобразования при совпадении порядка байтов.

Matthieu M. 01.11.2013 15:10

@meowsqueak: Да, я ожидал, что это действительно сработает, потому что меняется только порядок байтов, а не порядок битов в каждом байте.

Matthieu M. 12.02.2014 11:13

В общем, связанный пост - неприятное чтение ... Этот парень, кажется, ценит краткость, но он предпочел написать длинную тираду обо всех этих плохих программистах, которые не так просвещены, как он, в отношении порядка байтов, вместо того, чтобы на самом деле объясняя ситуацию и ПОЧЕМУ его решение всегда работает.

Ad N 09.11.2016 14:46

Если вы используете этот метод, убедитесь, что вы привели свои данные в (unsigned char *)

joseph 02.02.2017 09:11

@joseph: почему unsigned char *? char * должен работать так же хорошо, как uint8_t *.

Kenji 04.06.2017 22:06

@Kenji: я не знал о uint8_t, вот почему;). Моя основная идея заключалась в том, чтобы использовать беззнаковый. Я попробовал этот ответ без подписи и получил несколько странных ответов.

joseph 10.06.2017 18:37

@joseph: Это действительно важный момент; если вы используете подписанный char, то data[x] повышается до int, который подписан, и data[x] << 24 может, следовательно, сдвигать 1 в бит знака, что является неопределенным поведением.

Matthieu M. 10.06.2017 22:52

@Brett Тогда будет сложнее, но все же возможно. Во-первых, убедитесь, что определены __STD_IEC_559__, FLT_RADIX == 2, sizeof(float) == 4 и sizeof(double) == 8. Отсканируйте uint32_t, извлеките знак, показатель степени и мантиссу. Затем используйте ldexp, чтобы получить float / double. Хитрые детали: exponent = scanned_exponent - 127 // or 1023 for double и mantissa = 1.0f + scanned_mantissa / 8388608.0f // or 18014398509481984.0 for double. Вместо этого для чтения вы используете frexp.

MarkWeston 18.12.2017 01:18

@AdN Даже если тебе это неприятно, Роб все равно прав. Если вы позволите вашему коду зависеть от порядка байтов вашей машины, даже если вам это не нужно, это так же плохо, как если бы вы сделали свой код зависимым от фазы луны.

Roland Illig 11.12.2020 18:45

Обратите внимание, что, по крайней мере, для Windows, htonl () намного медленнее, чем их внутренний аналог _byteswap_ulong (). Первый - это вызов библиотеки DLL в ws2_32.dll, второй - это одна инструкция сборки BSWAP. Поэтому, если вы пишете код, зависящий от платформы, для скорости лучше использовать встроенные функции:

#define htonl(x) _byteswap_ulong(x)

Это может быть особенно важно для обработки изображений .PNG, когда все целые числа сохраняются в формате Big Endian с объяснением «Можно использовать htonl () ...» {для замедления типичных программ Windows, если вы не готовы}.

Если вы возьмете общий шаблон для изменения порядка битов в слове и выберете ту часть, которая меняет местами биты в каждом байте, то у вас останется что-то, что меняет местами только байты в слове. Для 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);

Серьезно ... Я не понимаю, почему все решения такие сложно! Как насчет простейшей, наиболее общей функции шаблона, которая меняет местами любой тип любого размера при любых обстоятельствах в любой операционной системе ????

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: Без операторов: помните, что я не использовал простой оператор присваивания «=», потому что некоторые объекты будут испорчены, когда порядок байтов будет изменен, а конструктор копирования (или оператор присваивания) не будет работать. Поэтому надежнее копировать их char за char.

Пункт 2: помните о проблемах с выравниванием: обратите внимание, что мы копируем в массив и из массива, что является правильным, потому что компилятор C++ не гарантирует, что мы можем получить доступ к невыровненной памяти (этот ответ был обновлен из его исходной формы за это). Например, если вы выделяете uint64_t, ваш компилятор не может гарантировать, что вы можете получить доступ к третьему байту этого байта как uint8_t. Поэтому правильнее всего скопировать это в массив символов, поменять местами, а затем скопировать обратно (так что никакого reinterpret_cast). Обратите внимание, что компиляторы в большинстве своем достаточно умны, чтобы преобразовать то, что вы сделали, обратно в reinterpret_cast, если они способны обращаться к отдельным байтам независимо от выравнивания.

Чтобы использовать эту функцию:

double x = 5;
SwapEnd(x);

и теперь x отличается порядком байтов.

Это будет работать где угодно, но производимый код сборки часто будет неоптимальным: см. Мой вопрос stackoverflow.com/questions/36657895/…

j_kubik 16.04.2016 14:36

Вы используете new / delete для выделения буфера для этого?!? sizeof(var) - это константа времени компиляции, поэтому вы можете использовать char varSwapped[sizeof(var)]. Или вы можете сделать char *p = reinterpret_cast<char*>(&var) и заменить его на месте.

Peter Cordes 26.04.2017 19:10

@Peter, этот ответ быстрый и грязный, чтобы доказать свою точку зрения. Реализую ваши предложения. Однако вам не нужно быть мега SO AH и голосовать против 5-строчного решения по сравнению с 50-строчным, от которого отказались. Больше я не скажу.

The Quantum Physicist 26.04.2017 19:16

В этом ответе содержится ряд полезных замечаний относительно осторожности с конструкторами и перегруженными операторами для данных с неправильным порядком байтов, поэтому я был бы рад удалить свой отрицательный голос, когда код не будет ужасным, и это то, что хороший компилятор мог бы скомпилировать в bswap инструкция. Кроме того, я бы предложил использовать for(size_t i = 0 ; i < sizeof(var) ; i++) вместо static_cast<long>. (Или на самом деле своп на месте будет использовать восходящий и нисходящий char*, так что он все равно исчезнет).

Peter Cordes 26.04.2017 19:23

например см. Ответ Марка Рэнсома с использованием std :: swap для обратного преобразования на месте.

Peter Cordes 26.04.2017 19:24

@PeterCordes Я не знал, что существует такой ответ. Во всяком случае, я улучшил свой ответ.

The Quantum Physicist 29.04.2017 19:13

Спасибо, что нашли время сделать это; теперь выглядит намного лучше. Умный компилятор мог бы скомпилировать это в инструкцию по порядку байтов, например bswap, если бы он использовался для целых чисел правильного размера. (Но, вероятно, все еще нет: /)

Peter Cordes 30.04.2017 04:52

Почему static_cast<long>, а не просто size_t i? См. C++ 11, §5.3.3: "Результат sizeof и sizeof ... - это константа типа std :: size_t"

rustyx 18.02.2019 20:02

@rustyx сила привычки :-). Я всегда использую циклы входа в систему, чтобы избежать потери значимости в определенных случаях, и провожу сравнение, чтобы избежать предупреждений.

The Quantum Physicist 18.02.2019 20:11

Если 32-разрядное целое число без знака с прямым порядком байтов выглядит как 0xAABBCCDD, которое равно 2864434397, то это же 32-разрядное целое число без знака выглядит как 0xDDCCBBAA на процессоре с прямым порядком байтов, который также равен 2864434397.

Если 16-битное короткое замыкание без знака с прямым порядком байтов выглядит как 0xAABB, которое равно 43707, то такое же 16-разрядное короткое замыкание без знака выглядит как 0xBBAA на процессоре с прямым порядком байтов, которое также равно 43707.

Вот несколько удобных функций #define для обмена байтами с прямого порядка байтов на обратный порядок байтов и наоборот ->

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

Вау, я не мог поверить некоторым ответам, которые прочитал здесь. На самом деле есть инструкция по сборке, которая делает это быстрее, чем что-либо другое. bswap. Вы могли бы просто написать такую ​​функцию ...

__declspec(naked) uint32_t EndianSwap(uint32 value)
{
    __asm
    {
        mov eax, dword ptr[esp + 4]
        bswap eax
        ret
    }
}

Это МНОГО быстрее, чем предполагалось. Разобрал, посмотрел. Вышеупомянутая функция не имеет пролога / эпилога, поэтому практически не имеет накладных расходов.

unsigned long _byteswap_ulong(unsigned long value);

Сделать 16 бит так же просто, за исключением того, что вы использовали бы xchg al, ах. bswap работает только с 32-битными регистрами.

64-битная версия немного сложнее, но не слишком. Намного лучше, чем все приведенные выше примеры с циклами, шаблонами и т. д.

Здесь есть некоторые предостережения ... Во-первых, bswap доступен только на процессорах 80x486 и выше. Кто-нибудь планирует запустить его на 386?!? Если это так, вы все равно можете заменить bswap на ...

mov ebx, eax
shr ebx, 16
xchg bl, bh
xchg al, ah
shl eax, 16
or eax, ebx

Также встроенная сборка доступна только в коде x86 в Visual Studio. Голая функция не может быть выровнена и также недоступна в сборках x64. В этом случае вам придется использовать встроенные функции компилятора.

_byteswap_ulong и _uint64 (например, в принятом ответе) компилируются для использования инструкции bswap. Я был бы удивлен, но мне было бы интересно узнать, действительно ли этот asm намного быстрее, поскольку он пропускает только пролог / эпилог - вы его тестировали?

ZachB 28.10.2015 08:54

@stdcall Вопрос не касался портативного решения и даже ничего не упоминал о платформе. Как сказал мой ответ, приведенное выше является самым быстрым способом замены байтов. Конечно, если вы пишете это на платформе, отличной от X86, это не сработает, но, как я также упоминал, вы ограничены встроенными функциями компилятора, если ваш компилятор даже поддерживает их.

The Welder 11.07.2016 12:54

@ZachB В этом конкретном случае, я думаю, что пропуск пролога и эпилога даст вам приличную экономию, потому что вы, по сути, выполняете только одну инструкцию. Пролог нужно будет поместить в стек, выполнить вычитание, установить базовый указатель и затем сделать то же самое в конце. Я не тестировал его, но у приведенного выше есть цепочка зависимостей 0, которую вы просто не получите, если она не будет обнаженной. Может быть, хороший компилятор встроит его, но тогда вы находитесь в другом парке.

The Welder 11.07.2016 12:59

Возможно. Но обратите внимание, что в общем случае замены массива чисел встроенные функции компилятора, обсуждаемые в других ответах, будут использовать расширения SSE / AVX и генерировать PSHUFB, что превосходит BSWAP. См. wm.ite.pl/articles/reverse-array-of-bytes.html

ZachB 11.07.2016 20:34

Это плохой тон, IMHO, публиковать решение для конкретной платформы, когда OP не указал, что им нужно решение только для x86. И чтобы унизить другие решения, когда ваше невозможно использовать на многих очень широко используемых ОС, таких как iOS и Android (которые используют процессоры ARM или MIPS).

Jens Alfke 31.07.2016 02:02

@Jens Alfke Когда был опубликован вопрос, не было упоминания об архитектуре или даже о том, какой размер байтового свопа они хотели. Я знаю, что такое решение не будет работать на архитектурах, отличных от x86, но я упомянул архитектуру, для которой оно предназначено. Кроме того, принятый ответ содержит типы и функции, специфичные для платформы, и больше не будет работать на iOS или Android, как мой. Я взялся за пунт и угадал, чего хочет пользователь, основываясь на неясном вопросе. Я не собирался оскорблять, я просто подумал, что некоторые из ответов были плохими, учитывая, что большинство из них немного менялись.

The Welder 04.08.2016 17:55

Это не может быть встроено. Внутренняя функция компилятора должна в соответствии для одной инструкции bswap, но этот ответ заставляет компилятор фактически выполнить вызов функции. Вы сравнивали вывод asm с отключенной оптимизацией?

Peter Cordes 26.04.2017 19:14

Просто подумал, что добавил сюда свое собственное решение, так как нигде его не видел. Это небольшая и переносимая шаблонная функция C++, которая использует только битовые операции.

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

Переносимый метод для реализации удобных для оптимизатора невыровненных методов доступа без порядка байтов. Они работают с каждым компилятором, с каждым выравниванием границ и каждым порядком байтов. Эти невыровненные процедуры дополняются или обсуждаются в зависимости от собственного порядка байтов и выравнивания. Частичный листинг, но идею вы поняли. BO * - постоянные значения, основанные на собственном порядке байтов.

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 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 */

В этом вопросе тег C++ имеет значение. Есть много неопределенного поведения из-за C++ и объединения.

jww 12.04.2017 21:35

Используя приведенные ниже коды, вы можете легко переключаться между Big Endian и Little Endian.

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

Недавно я написал макрос для этого на C, но он также действителен и на 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)

Он принимает любой тип и меняет байты в переданном аргументе. Примеры использования:

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

Какие отпечатки:

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

Вышеупомянутое идеально подходит для копирования / вставки, но здесь много всего происходит, поэтому я расскажу, как это работает, по частям:

Первое, что следует отметить, это то, что весь макрос заключен в блок do while(0). Это общая идиома, позволяющий использовать обычную точку с запятой после макроса.

Далее следует использование переменной REVERSE_BYTES в качестве счетчика цикла for. Имя самого макроса используется в качестве имени переменной, чтобы гарантировать, что оно не конфликтует с любыми другими символами, которые могут быть в области видимости везде, где используется макрос. Поскольку имя используется в раскрытии макроса, оно не будет снова расширено при использовании здесь в качестве имени переменной.

В цикле for имеется ссылка на два байта и XOR поменялся местами (поэтому временное имя переменной не требуется):

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

__VA_ARGS__ представляет все, что было дано макросу, и используется для увеличения гибкости того, что может быть передано (хотя и не намного). Затем берется адрес этого аргумента и приводится к указателю unsigned char, чтобы разрешить перестановку его байтов через индексирование массива [].

И последняя особенность - отсутствие скоб {}. В них нет необходимости, потому что все шаги в каждом свопе объединяются с помощью оператор запятой, что делает их одним оператором.

Наконец, стоит отметить, что это не идеальный подход, если скорость является главным приоритетом. Если это важный фактор, вероятно, лучшим вариантом будут некоторые из макросов для конкретных типов или директив для конкретных платформ, упомянутых в других ответах. Однако этот подход переносим для всех типов, всех основных платформ и языков C и C++.

нашел это где-то в каком-то коде. запутал меня до чертиков. Спасибо за объяснение. Однако зачем использовать __VA_ARGS__?

asr9 30.07.2019 12:50

Я очень удивлен, что никто не упомянул функции htobeXX и betohXX. Они определены в endian.h и очень похожи на сетевые функции htonXX.

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

/*
* 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

Обмен байтами с помощью старого трехэтапного трюка с xor вокруг поворота в функции шаблона дает гибкое и быстрое решение O (ln2), которое не требует библиотеки, стиль здесь также отклоняет однобайтовые типы:

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

Похоже, что безопасным способом было бы использовать htons для каждого слова. Итак, если у вас есть ...

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

Вышеупомянутое было бы невозможным, если бы вы были в системе с прямым порядком байтов, поэтому я бы поискал все, что ваша платформа использует в качестве условия времени компиляции, чтобы решить, является ли htons закрытым. В конце концов, это O (n). На Mac это будет что-то вроде ...

#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

Если у вас 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);

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

void endianness_swap(uint32_t& val) {
    uint8_t a, b, c;
    a = (val & 0xFF000000) >> 24;
    b = (val & 0x00FF0000) >> 16;
    c = (val & 0x0000FF00) >> 8;
    val=(val & 0x000000FF) << 24;
    val = val + (c << 16) + (b << 8) + (a);
}

Другие вопросы по теме