¿Cómo convierto entre valores big-endian y little-endian en C++?
¿Cómo convierto entre valores big-endian y little-endian en C++?
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í.
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.
Si está utilizando Visual C++, haga lo siguiente: incluya intrin.h y llame 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 GCC, por ejemplo, puedes llamar directamente a algunas funciones integradas como se documenta aquí :
uint32_t __builtin_bswap32 (uint32_t x)
uint64_t __builtin_bswap64 (uint64_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.
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 de Rob Pike:
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) | ((unsigned)data[3]<<24);
Si es big-endian, aquí se explica cómo extraerlo:
i = (data[3]<<0) | (data[2]<<8) | (data[1]<<16) | ((unsigned)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 1: Se espera que aquí int
sean unsigned int
32 bits; de lo contrario, es posible que los tipos requieran ajustes.
Nota 2: El último byte se debe convertir explícitamente unsigned
antes del cambio, ya que de forma predeterminada se promueve a int
, y un cambio de 24 bits significa manipular el bit de signo, que es un comportamiento indefinido.
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>
#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);
}
Luego 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);