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

10 minutos de lectura

¿Como convierto entre valores big endian y little endian en C
Uhall

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

Para mayor claridad, tengo que traducir datos binarios (valores de coma 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í.


Nota: 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.

  • ntoh hton funcionará bien, incluso si no tiene nada que ver con la creación de redes.

    – Ben Collins

    20 de septiembre de 2008 a las 4:31

  • La mejor manera de lidiar con endianness en general es asegurarse de que el código se ejecute en máquinas host de endian pequeño y grande. Si eso funciona, probablemente lo hiciste bien. Asumir que está en x86/be es peligroso como práctica.

    – jakobengblom2

    6 oct 2008 a las 20:19

  • hton ntoh no funcionará si la máquina es big-endian, porque el autor de la pregunta desea explícitamente realizar la conversión.

    – fabspro

    17 de noviembre de 2011 a las 8:38

  • @jakobengblom2 es la única persona que menciona esto. Casi todos los ejemplos en esta página usan conceptos como bytes de “intercambio” en lugar de hacerlo agnóstico de la endianidad subyacente. Si está tratando con formatos de archivo externos (que tienen endianness bien definido), entonces lo más portátil que puede hacer es tratar los datos externos como un flujo de bytes y convertir el flujo de bytes hacia y desde los enteros nativos. Me estremezco cada vez que veo short swap(short x) código, ya que se romperá si te mueves a una plataforma con endianness diferente. Matthieu M tiene la única respuesta correcta a continuación.

    –Mark Lakata

    8 de marzo de 2013 a las 18:16

  • Estás pensando en el problema completamente mal. La tarea no es “¿cómo convierto entre valores big-endian y little-endian”. La tarea es “¿cómo convierto valores enteros y de punto flotante en un formato particular al formato nativo de mi plataforma?”. Si lo hace bien, el formato nativo puede ser big endian, little endian, mixed endian o ternario para todos los aspectos de su código.

    –David Schwartz

    7 de agosto de 2014 a las 8:20

1647565390 139 ¿Como convierto entre valores big endian y little endian en C
alejandro c

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

  • Tener un voto a favor. Solo usé uchars y asigné 4 a 1, 3 a 2, 2 a 3 y 1 a 4, pero esto es más flexible si tiene diferentes tamaños. 6 relojes en un Pentium IIRC de primera generación. BSWAP es 1 reloj, pero es específico de la plataforma.

    usuario1899861

    1 de marzo de 2013 a las 19:53

  • @RocketRoy: Sí, y si la velocidad resulta ser un problema, es muy sencillo escribir sobrecargas con intrínsecos específicos de plataforma y tipo.

    – Alejandro C.

    14 de julio de 2013 a las 11:23

  • @MihaiTodor: el estándar permite explícitamente este uso de uniones para encasillar a través de una matriz de caracteres. Véase, por ejemplo. esta pregunta.

    – Alejandro C.

    4 mayo 2014 a las 12:36

  • @AlexandreC. No en el estándar C++, solo en C. En C++ (que es este código), este código tiene un comportamiento indefinido.

    – Rapptz

    22 de julio de 2014 a las 22:54

  • @Rapptz: 3.10 parece claro: “Si un programa intenta acceder al valor almacenado de un objeto a través de un glvalue que no sea uno de los siguientes tipos, el comportamiento no está definido: […] un tipo char o char sin firmar.“. Tal vez me estoy perdiendo algo aquí, pero estaba bastante claro para mí que acceder a cualquier tipo a través de punteros de caracteres estaba explícitamente permitido.

    – Alejandro C.

    23 de julio de 2014 a las 18:20


  • Esto es genial, pero me parece que solo se aplica a los números enteros y las variantes. ¿Qué hacer con los flotadores/dobles?

    – Brett

    5 de enero de 2013 a las 5:04

  • @v.oddou: sí y no, los archivos mapeados en memoria son exactamente iguales a los marcos de red; si usted acepta no para leerlos directamente, lo único que importa es sus endianness: si es little-endian, use la primera fórmula, si es big-endian, use la segunda. Cualquier compilador que se precie optimizará las transformaciones innecesarias si el endianness coincide.

    – Matthieu M.

    1 de noviembre de 2013 a las 11:10

  • @meowsqueak: Sí, espero que funcione, porque solo cambia el orden de los bytes, no el orden de los bits dentro de cada byte.

    – Matthieu M.

    12 de febrero de 2014 a las 7:13


  • En una nota vagamente relacionada, la publicación vinculada es una lectura desagradable… El tipo parece valorar la brevedad, sin embargo, prefirió escribir una larga diatriba sobre todos esos malos programadores que no están tan informados como él con respecto a endianness, en lugar de realmente explicando la situación y POR QUÉ su solución siempre funciona.

    – Anuncio N

    9 de noviembre de 2016 a las 11:46


  • Si está utilizando este método, asegúrese de enviar sus datos a (caracter sin firmar *)

    – José

    2 de febrero de 2017 a las 6:11

Si está haciendo esto con fines 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 alguna otra razón, una de las soluciones byte_swap presentadas aquí funcionaría bien.

  • el orden de bytes de red es big endian, creo. Estas funciones se pueden usar con eso en mente incluso si no está usando un código de red. Sin embargo, no hay versiones flotantes ntohf o htonf

    – hookenz

    13 de noviembre de 2010 a las 20:33

  • Matt H. eso es mayormente correcto. No todos los sistemas informáticos tienen un orden de bytes little-endian. Si estaba trabajando en, digamos, un motorolla 68k, un PowerPC u otra arquitectura big-endian, estas funciones no intercambiarán bytes en absoluto porque ya están en ‘Orden de bytes de red’.

    – Escarchado

    15 de noviembre de 2010 a las 14:36

  • Desafortunadamente, htonl y ntohl no puede ir a little endian en una plataforma big-endian.

    – Brian Vandenberg

    25 de abril de 2012 a las 16:10

  • @celtschk, entendido; sin embargo, el OP quiere una forma de cambiar el endianismo, incluso en un entorno big-endian.

    – Brian Vandenberg

    22 de julio de 2013 a las 16:32

  • Para evitar la pregunta inevitable: hay una serie de razones para necesitar LE para una plataforma BE; una cantidad de formatos de archivo (bmp, fli, pcx, qtm, rtf, tga, por nombrar algunos) usan valores little endian… o al menos, alguna versión del formato lo hizo alguna vez.

    – Brian Vandenberg

    22 de julio de 2013 a las 16:38

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

Entonces lo usarías de la siguiente manera:

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

y viceversa

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

  • el orden de bytes de red es big endian, creo. Estas funciones se pueden usar con eso en mente incluso si no está usando un código de red. Sin embargo, no hay versiones flotantes ntohf o htonf

    – hookenz

    13 de noviembre de 2010 a las 20:33

  • Matt H. eso es mayormente correcto. No todos los sistemas informáticos tienen un orden de bytes little-endian. Si estaba trabajando en, digamos, un motorolla 68k, un PowerPC u otra arquitectura big-endian, estas funciones no intercambiarán bytes en absoluto porque ya están en ‘Orden de bytes de red’.

    – Escarchado

    15 de noviembre de 2010 a las 14:36

  • Desafortunadamente, htonl y ntohl no puede ir a little endian en una plataforma big-endian.

    – Brian Vandenberg

    25 de abril de 2012 a las 16:10

  • @celtschk, entendido; sin embargo, el OP quiere una forma de cambiar el endianismo, incluso en un entorno big-endian.

    – Brian Vandenberg

    22 de julio de 2013 a las 16:32

  • Para evitar la pregunta inevitable: hay una serie de razones para necesitar LE para una plataforma BE; una cantidad de formatos de archivo (bmp, fli, pcx, qtm, rtf, tga, por nombrar algunos) usan valores little endian… o al menos, alguna versión del formato lo hizo alguna vez.

    – Brian Vandenberg

    22 de julio de 2013 a las 16:38

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

  • La última función publicada aquí es incorrecta y debe editarse para: void swapByteOrder(unsigned long long& ull) { ull = (ull >> 56) | … (ultimo << 56); }

    –Eric Burnett

    19 de septiembre de 2008 a las 20:58

  • No creo que sea correcto usar lógico y (&&) en lugar de bit a bit y (&). De acuerdo con la especificación de C++, ambos operandos se convierten implícitamente a bool, que no es lo que desea.

    – Trevor Robinson

    23 de junio de 2009 a las 21:04

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad