Pregunta

¿Cómo convierto entre valores big-endian y little-endian en C++?

EDITAR:Para mayor claridad, tengo que traducir datos binarios (valores de punto flotante de doble precisión y enteros de 32 y 64 bits) de una arquitectura de CPU a otra.Esto no implica redes, por lo que ntoh() y funciones similares no funcionarán aquí.

EDITAR #2:La respuesta que acepté se aplica directamente a los compiladores a los que me dirijo (por eso la elegí).Sin embargo, aquí hay otras respuestas muy buenas y más portátiles.

¿Fue útil?

Solución

Si estas usando VisualC++ Haz lo siguiente:Incluye intrin.h y llama a las siguientes funciones:

Para números de 16 bits:

unsigned short _byteswap_ushort(unsigned short value);

Para números de 32 bits:

unsigned long _byteswap_ulong(unsigned long value);

Para números de 64 bits:

unsigned __int64 _byteswap_uint64(unsigned __int64 value);

No es necesario convertir los números de 8 bits (caracteres).

Además, estos solo se definen para valores sin signo y también funcionan para enteros con signo.

Para flotantes y dobles es más difícil que con números enteros simples, ya que pueden estar o no en el orden de bytes de las máquinas host.Puede obtener flotadores little-endian en máquinas big-endian y viceversa.

Otros compiladores también tienen características intrínsecas similares.

En CCG por ejemplo puedes llamar directamente:

int32_t __builtin_bswap32 (int32_t x)
int64_t __builtin_bswap64 (int64_t x)

(no es necesario incluir nada).Afaik bits.h también declara la misma función de una manera no centrada en gcc.

El intercambio de 16 bits es solo una rotación de bits.

Llamar a los intrínsecos en lugar de implementar los suyos propios le brinda el mejor rendimiento y densidad de código, por cierto.

Otros consejos

Simplemente pon:

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

uso: swap_endian<uint32_t>(42).

De La falacia del orden de bytes por Rob Pyke:

Digamos que su flujo de datos tiene un entero de 32 bits codificado en little-endian.Aquí se explica cómo extraerlo (asumiendo bytes sin firmar):

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

Si es big-endian, aquí se explica cómo extraerlo:

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

TL;DR: No se preocupe por el orden nativo de su plataforma, todo lo que cuenta es el orden de bytes de la transmisión que está leyendo, y será mejor que esté bien definido.

Nota:Se comentó en el comentario que, en ausencia de una conversión de tipo explícita, era importante que data ser una serie de unsigned char o uint8_t.Usando signed char o char (si se firma) resultará en data[x] siendo promovido a un número entero y data[x] << 24 potencialmente cambiando un 1 al bit de signo que es UB.

Si está haciendo esto por motivos de compatibilidad de red/host, debe usar:

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)

Si está haciendo esto por algún otro motivo, una de las soluciones byte_swap presentadas aquí funcionaría bien.

Tomé algunas sugerencias de esta publicación y las junté para formar esto:

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

Hay una instrucción de ensamblaje llamada BSWAP que hará el intercambio por usted. extremadamente rápido.Puedes leer sobre esto. aquí.

Visual Studio, o más precisamente la biblioteca de tiempo de ejecución de Visual C++, tiene una plataforma intrínseca para esto, llamada _byteswap_ushort(), _byteswap_ulong(), and _byteswap_int64().Debería existir algo similar para otras plataformas, pero no sé cómo se llamarían.

El procedimiento para pasar de big-endian a little-endian es el mismo que para pasar de little-endian a big-endian.

Aquí hay un código de ejemplo:

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

Hemos hecho esto con plantillas.Podrías hacer algo como esto:

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

Si estás haciendo esto para transferir datos entre diferentes plataformas, mira las funciones ntoh y hton.

De la misma manera que lo haces en C:

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

También puede declarar un vector de caracteres sin firmar, memorizar el valor de entrada en él, invertir los bytes en otro vector y memorizar los bytes, pero eso llevará órdenes de magnitud más largos que manipular bits, especialmente con valores de 64 bits.

En la mayoría de los sistemas POSIX (aunque no está en el estándar POSIX) existe endian.h, que se puede utilizar para determinar qué codificación utiliza su sistema.A partir de ahí es algo como esto:

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

Esto cambia el orden (de big-endian a little endian):

Si tiene el número 0xDEADBEEF (en un sistema little endian almacenado como 0xEFBEADDE), ptr[0] será 0xEF, ptr[1] será 0xBE, etc.

Pero si desea usarlo para redes, entonces htons, htonl y htonll (y sus ntohs, ntohl y ntohll inversos) serán útiles para convertir del orden de host al orden de red.

Tenga en cuenta que, al menos para Windows, htonl() es mucho más lento que su contraparte intrínseca _byteswap_ulong().La primera es una llamada de biblioteca DLL a ws2_32.dll, la segunda es una instrucción ensambladora BSWAP.Por lo tanto, si está escribiendo código que depende de la plataforma, prefiera usar los intrínsecos para mayor velocidad:

#define htonl(x) _byteswap_ulong(x)

Esto puede ser especialmente importante para el procesamiento de imágenes .PNG donde todos los números enteros se guardan en Big Endian con la explicación "Se puede usar htonl()..." {para ralentizar los programas típicos de Windows, si no está preparado}.

La mayoría de las plataformas tienen un archivo de encabezado del sistema que proporciona funciones eficientes de intercambio de bytes.En Linux está en <endian.h>.Puedes envolverlo muy bien en 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';
}

Producción:

cafe
deadbeaf
feeddeafbeefcafe

Me gusta este, sólo por estilo :-)

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

En serio...No entiendo por qué todas las soluciones son esa. complicado! ¿Qué tal la función de plantilla más simple y general que intercambia cualquier tipo de cualquier tamaño bajo cualquier circunstancia en cualquier sistema operativo?

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

¡Es el poder mágico de C y C++ juntos!Simplemente intercambie la variable original carácter por carácter.

Recuerde que no utilicé el operador de asignación simple "=" porque algunos objetos se estropearán cuando se invierta el endianismo y el constructor de copia (u operador de asignación) no funcionará.Por lo tanto, es más confiable copiarlos carácter por carácter.

Para llamarlo, solo usa

double x = 5;
SwapEnd(x);

y ahora x es diferente en endianidad.

Tengo este código que me permite convertir de HOST_ENDIAN_ORDER (sea lo que sea) a LITTLE_ENDIAN_ORDER o BIG_ENDIAN_ORDER.Utilizo una plantilla, por lo que si intento convertir de HOST_ENDIAN_ORDER a LITTLE_ENDIAN_ORDER y resultan ser los mismos para la máquina para la que compilo, no se generará ningún código.

Aquí está el código con algunos comentarios:

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

Si un entero big-endian sin signo de 32 bits se parece a 0xAABBCCDD, que es igual a 2864434397, entonces ese mismo entero sin signo de 32 bits se parece a 0xDDCCBBAA en un procesador little-endian que también es igual a 2864434397.

Si un corto sin signo big-endian de 16 bits se parece a 0xAABB, que es igual a 43707, entonces ese mismo corto sin signo de 16 bits se parece a 0xBBAA en un procesador little-endian que también es igual a 43707.

Aquí hay un par de funciones #define útiles para intercambiar bytes de little-endian a big-endian y viceversa -->

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

Aquí hay una versión generalizada que se me ocurrió, para intercambiar un valor en su lugar.Las otras sugerencias serían mejores si el rendimiento es un problema.

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

Descargo de responsabilidad: No he intentado compilar esto ni probarlo todavía.

Si toma el patrón común para invertir el orden de los bits en una palabra y elimina la parte que invierte los bits dentro de cada byte, entonces quedará algo que solo invierte los bytes dentro de una palabra.Para 64 bits:

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

el compilador debería limpie las operaciones de enmascaramiento de bits superfluas (las dejé para resaltar el patrón), pero si no es así, puede reescribir la primera línea de esta manera:

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

Normalmente, eso debería simplificarse a una sola instrucción de rotación en la mayoría de las arquitecturas (ignorando que toda la operación es probablemente una sola instrucción).

En un procesador RISC, las constantes grandes y complicadas pueden causar dificultades al compilador.Sin embargo, puedes calcular trivialmente cada una de las constantes de la anterior.Al igual que:

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

Si lo deseas, puedes escribirlo como un bucle.No será eficiente, sino sólo por diversión:

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

Y para completar, aquí está la versión simplificada de 32 bits del primer formulario:

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

Solo pensé en agregar mi propia solución aquí ya que no la he visto en ninguna parte.Es una función con plantilla de C++ pequeña y portátil que solo utiliza operaciones de bits.

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

Con los códigos que se proporcionan a continuación, puede intercambiar entre BigEndian y LittleEndian fácilmente

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

Realmente me sorprende que nadie haya mencionado las funciones htobeXX y betohXX.Están definidas en endian.h y son muy similares a las funciones de red htonXX.

Vaya, no podía creer algunas de las respuestas que he leído aquí.De hecho, hay una instrucción de ensamblaje que hace esto más rápido que cualquier otra cosa.intercambio.Simplemente podrías escribir una función como esta...

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

Es MUCHO más rápido que los intrínsecos que se han sugerido.Los desmonté y miré.La función anterior no tiene prólogo/epílogo, por lo que prácticamente no tiene ningún tipo de sobrecarga.

unsigned long _byteswap_ulong(unsigned long value);

Hacer 16 bits es igual de fácil, con la excepción de que usarías xchg al, ah.bswap sólo funciona en registros de 32 bits.

64 bits es un poco más complicado, pero no demasiado.Mucho mejor que todos los ejemplos anteriores con bucles y plantillas, etc.

Hay algunas advertencias aquí...En primer lugar, bswap sólo está disponible en CPU de 80x486 y superiores.¿Alguien planea ejecutarlo en un 386?Si es así, aún puedes reemplazar bswap con...

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

Además, el ensamblaje en línea solo está disponible en código x86 en Visual Studio.Una función simple no se puede alinear y tampoco está disponible en compilaciones x64.En ese caso, tendrás que usar los intrínsecos del compilador.

Técnica portátil para implementar descriptores de acceso endian no alineados y no instalados compatibles con el optimizador.Trabajan en cada compilador, cada alineación de límites y cada orden de bytes.Estas rutinas no alineadas se complementan o se discuten según el endian nativo y la alineación.Listado parcial pero ya entiendes la idea.BO* son valores constantes basados ​​en el orden de bytes nativo.

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

Estos typedefs tienen la ventaja de generar errores del compilador si no se usan con descriptores de acceso, mitigando así errores de descriptores de acceso olvidados.

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

Recientemente escribí una macro para hacer esto en C, pero es igualmente válida en 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)

Acepta cualquier tipo e invierte los bytes en el argumento pasado.Usos de ejemplo:

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

Que imprime:

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

Lo anterior se puede copiar y pegar perfectamente, pero están sucediendo muchas cosas aquí, así que explicaré cómo funciona pieza por pieza:

La primera cosa notable es que toda la macro está encerrada en un do while(0) bloquear.Esto es un modismo común para permitir el uso normal de punto y coma después de la macro.

El siguiente paso es el uso de una variable llamada REVERSE_BYTES como el for contador del bucle.El nombre de la macro en sí se utiliza como nombre de variable para garantizar que no entre en conflicto con ningún otro símbolo que pueda estar dentro del alcance dondequiera que se utilice la macro.Dado que el nombre se usa dentro de la expansión de la macro, no se expandirá nuevamente cuando se use aquí como nombre de variable.

Dentro de for bucle, hay dos bytes a los que se hace referencia y XOR intercambiado (por lo que no se requiere un nombre de variable temporal):

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

__VA_ARGS__ representa lo que se le dio a la macro y se usa para aumentar la flexibilidad de lo que se puede pasar (aunque no mucho).Luego se toma la dirección de este argumento y se transmite a un unsigned char puntero para permitir el intercambio de sus bytes a través de una matriz [] subscripción.

El último punto peculiar es la falta de {} tirantes.No son necesarios porque todos los pasos de cada swap están unidos con el operador de coma, haciéndolos una declaración.

Finalmente, vale la pena señalar que este no es el enfoque ideal si la velocidad es la máxima prioridad.Si este es un factor importante, algunas de las macros específicas de tipo o directivas específicas de plataforma a las que se hace referencia en otras respuestas probablemente sean una mejor opción.Este enfoque, sin embargo, es portátil para todos los tipos, todas las plataformas principales y los lenguajes C y C++.

Intentar Boost::endian, ¡Y NO LO IMPLEMENTE USTED MISMO!

Aquí está un enlace

Aquí se explica cómo leer un archivo doble almacenado en formato IEEE 754 de 64 bits, incluso si su computadora host usa un sistema diferente.

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

Para el resto del conjunto de funciones, incluidas las rutinas de escritura y de números enteros, consulte mi proyecto github.

https://github.com/MalcolmMcLean/ieee754

El intercambio de bytes con el antiguo truco xor de 3 pasos alrededor de un pivote en una función de plantilla proporciona una solución O(ln2) flexible y rápida que no requiere una biblioteca; el estilo aquí también rechaza los tipos de 1 byte:

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

Parece que la forma segura sería usar htons en cada palabra.Entonces, si tienes...

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

Lo anterior no sería operativo si estuviera en un sistema big-endian, por lo que buscaría lo que sea que use su plataforma como condición de tiempo de compilación para decidir si htons no es operativo.Después de todo, es O(n).En una Mac, sería algo así como...

#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

Busque cambios de bits, ya que esto es básicamente todo lo que necesita hacer para cambiar de little -> big endian.Luego, dependiendo del tamaño de la broca, cambia la forma de realizar el cambio de broca.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top