64 bit ntohl() en C++?
linux 64bit (16)
Las páginas man para htonl()
parecen sugerir que solo puede usarlo para valores de hasta 32 bits. (En realidad, ntohl()
se define para long sin signo, que en mi plataforma es de 32 bits. Supongo que si el unsigned long tuviera 8 bytes, funcionaría para 64 bit ints).
Mi problema es que necesito convertir enteros de 64 bits (en mi caso, este es un largo sin firmar) de big endian a little endian. En este momento, necesito hacer esa conversión específica. Pero sería aún más agradable si la función (como ntohl()
) NO convirtiera mi valor de 64 bits si la plataforma de destino fuera Big Endian. (Prefiero evitar agregar mi propia magia de preprocesador para hacer esto).
¿Qué puedo usar? Me gustaría algo que sea estándar si existe, pero estoy abierto a sugerencias de implementación. He visto este tipo de conversión hecha en el pasado usando uniones. Supongo que podría tener una unión con un unsigned long long y un char [8]. A continuación, cambie los bytes en consecuencia. (Obviamente se rompería en las plataformas que eran grandes endian).
Respuesta rápida
uint64_t value = 0x1122334455667788;
#if __BYTE_ORDER == __LITTLE_ENDIAN
value = __bswap_constant_64(value); // Compiler builtin
#endif
El resto de esta respuesta es sobre hton()
genérico hton()
para 16 bits, 32 bits, 64 bits ... en C ++ moderno.
constexpr
plantilla constexpr
C ++ 14
#include <endian.h> // __BYTE_ORDER
#include <algorithm> // std::reverse
template <typename T>
constexpr T htonT (T value) noexcept
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
char* ptr = reinterpret_cast<char*>(&value);
std::reverse (ptr, ptr + sizeof(T));
#endif
return value;
}
constexpr
plantilla constexpr
C ++ 11
- C ++ 11 no permite la variable local en la función
constexpr
.
Por lo tanto, el truco es usar un argumento con valor predeterminado. - Además, la función
constexpr
C ++ 11 debe contener una única expresión.
Por lo tanto, el cuerpo se compone de un retorno que tiene algunos enunciados separados por comas.
template <typename T>
constexpr T htonT (T value, char* ptr=0) noexcept
{
return
#if __BYTE_ORDER == __LITTLE_ENDIAN
ptr = reinterpret_cast<char*>(&value),
std::reverse (ptr, ptr + sizeof(T)),
#endif
value;
}
No hay advertencia de compilación en ambos clang-3.5 y GCC-4.9 usando -Wall -Wextra -pedantic
(ver compilación y ejecutar salida en coliru ).
C ++ 11 constexpr
template SFINAE funciones
Sin embargo, la versión anterior no permite crear variables constexpr
como:
constexpr int32_t hton_six = htonT( int32_t(6) );
Finalmente, necesitamos separar (especializar) las funciones dependiendo de 16/32/64 bits.
Pero aún podemos mantener funciones genéricas.
=> Ver el fragmento completo en coliru .
El siguiente fragmento de C ++ 11 usa los traits std::enable_if
para explotar el std::enable_if
de sustitución no es un error (SFINAE).
template <typename T>
constexpr typename std::enable_if<sizeof(T) == 2, T>::type
htonT (T value) noexcept
{
return ((value & 0x00FF) << 8)
| ((value & 0xFF00) >> 8);
}
template <typename T>
constexpr typename std::enable_if<sizeof(T) == 4, T>::type
htonT (T value) noexcept
{
return ((value & 0x000000FF) << 24)
| ((value & 0x0000FF00) << 8)
| ((value & 0x00FF0000) >> 8)
| ((value & 0xFF000000) >> 24);
}
template <typename T>
constexpr typename std::enable_if<sizeof(T) == 8, T>::type
htonT (T value) noexcept
{
return ((value & 0xFF00000000000000ull) >> 56)
| ((value & 0x00FF000000000000ull) >> 40)
| ((value & 0x0000FF0000000000ull) >> 24)
| ((value & 0x000000FF00000000ull) >> 8)
| ((value & 0x00000000FF000000ull) << 8)
| ((value & 0x0000000000FF0000ull) << 24)
| ((value & 0x000000000000FF00ull) << 40)
| ((value & 0x00000000000000FFull) << 56);
}
O una versión aún más corta basada en las macros compiladas incorporadas y la sintaxis de C ++ 14 std::enable_if_t<xxx>
como un acceso directo para std::enable_if<xxx>::type
:
template <typename T>
constexpr typename std::enable_if_t<sizeof(T) == 2, T>
htonT (T value) noexcept
{
return __bswap_constant_16(value);
}
template <typename T>
constexpr typename std::enable_if_t<sizeof(T) == 4, T>
htonT (T value) noexcept
{
return __bswap_constant_32(value);
}
template <typename T>
constexpr typename std::enable_if_t<sizeof(T) == 8, T>
htonT (T value) noexcept
{
return __bswap_constant_64(value);
}
Código de prueba de la primera versión
std::uint8_t uc = ''B''; std::cout <<std::setw(16)<< uc <<''/n'';
uc = htonT( uc ); std::cout <<std::setw(16)<< uc <<''/n'';
std::uint16_t us = 0x1122; std::cout <<std::setw(16)<< us <<''/n'';
us = htonT( us ); std::cout <<std::setw(16)<< us <<''/n'';
std::uint32_t ul = 0x11223344; std::cout <<std::setw(16)<< ul <<''/n'';
ul = htonT( ul ); std::cout <<std::setw(16)<< ul <<''/n'';
std::uint64_t uL = 0x1122334455667788; std::cout <<std::setw(16)<< uL <<''/n'';
uL = htonT( uL ); std::cout <<std::setw(16)<< uL <<''/n'';
Código de prueba de la segunda versión
constexpr uint8_t a1 = ''B''; std::cout<<std::setw(16)<<a1<<''/n'';
constexpr auto b1 = htonT(a1); std::cout<<std::setw(16)<<b1<<''/n'';
constexpr uint16_t a2 = 0x1122; std::cout<<std::setw(16)<<a2<<''/n'';
constexpr auto b2 = htonT(a2); std::cout<<std::setw(16)<<b2<<''/n'';
constexpr uint32_t a4 = 0x11223344; std::cout<<std::setw(16)<<a4<<''/n'';
constexpr auto b4 = htonT(a4); std::cout<<std::setw(16)<<b4<<''/n'';
constexpr uint64_t a8 = 0x1122334455667788;std::cout<<std::setw(16)<<a8<<''/n'';
constexpr auto b8 = htonT(a8); std::cout<<std::setw(16)<<b8<<''/n'';
Salida
B
B
1122
2211
11223344
44332211
1122334455667788
8877665544332211
Codigo de GENERACION
El compilador en línea de C ++ gcc.godbolt.org indica el código generado.
g++-4.9.2 -std=c++14 -O3
std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT<unsigned char>(unsigned char):
movl %edi, %eax
ret
std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT<unsigned short>(unsigned short):
movl %edi, %eax
rolw $8, %ax
ret
std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT<unsigned int>(unsigned int):
movl %edi, %eax
bswap %eax
ret
std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT<unsigned long>(unsigned long):
movq %rdi, %rax
bswap %rax
ret
clang++-3.5.1 -std=c++14 -O3
std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT<unsigned char>(unsigned char): # @std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT<unsigned char>(unsigned char)
movl %edi, %eax
retq
std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT<unsigned short>(unsigned short): # @std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT<unsigned short>(unsigned short)
rolw $8, %di
movzwl %di, %eax
retq
std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT<unsigned int>(unsigned int): # @std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT<unsigned int>(unsigned int)
bswapl %edi
movl %edi, %eax
retq
std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT<unsigned long>(unsigned long): # @std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT<unsigned long>(unsigned long)
bswapq %rdi
movq %rdi, %rax
retq
Nota: mi respuesta original no era compatible con C ++ 11- constexpr
.
Esta respuesta es en Dominio Público CC0 1.0 Universal
¿Qué tal una versión genérica, que no depende del tamaño de entrada (algunas de las implementaciones anteriores suponen que unsigned long long
es 64 bits, lo que no siempre es necesariamente cierto):
// converts an arbitrary large integer (preferrably >=64 bits) from big endian to host machine endian
template<typename T> static inline T bigen2host(const T& x)
{
static const int one = 1;
static const char sig = *(char*)&one;
if (sig == 0) return x; // for big endian machine just return the input
T ret;
int size = sizeof(T);
char* src = (char*)&x + sizeof(T) - 1;
char* dst = (char*)&ret;
while (size-- > 0) *dst++ = *src--;
return ret;
}
Documentación: man htobe64
en Linux (glibc> = 2.9) o FreeBSD.
Desafortunadamente, OpenBSD, FreeBSD y glibc (Linux) no funcionaron juntos sin problemas para crear un estándar de libc (no API de kernel) para esto, durante un intento en 2009.
Actualmente, este breve código de preprocesador:
#if defined(__linux__)
# include <endian.h>
#elif defined(__FreeBSD__) || defined(__NetBSD__)
# include <sys/endian.h>
#elif defined(__OpenBSD__)
# include <sys/types.h>
# define be16toh(x) betoh16(x)
# define be32toh(x) betoh32(x)
# define be64toh(x) betoh64(x)
#endif
(probado en Linux y OpenBSD) debería ocultar las diferencias. Te da las macros de estilo Linux / FreeBSD en esas 4 plataformas.
Use el ejemplo:
#include <stdint.h> // For ''uint64_t''
uint64_t host_int = 123;
uint64_t big_endian;
big_endian = htobe64( host_int );
host_int = be64toh( big_endian );
Es el enfoque más "estándar C biblioteca" -ish disponible en este momento.
En general, no es necesario conocer el endianness de una máquina para convertir un entero de host en una orden de red. Lamentablemente, esto solo se cumple si escribe el valor de su pedido neto en bytes, en lugar de otro entero:
static inline void short_to_network_order(uchar *output, uint16_t in)
{
output[0] = in>>8&0xff;
output[1] = in&0xff;
}
(extienda según sea necesario para números más grandes).
Esto (a) trabajará en cualquier arquitectura, porque en ningún momento utilizo un conocimiento especial sobre la forma en que un entero se presenta en la memoria y (b) debería optimizarse en arquitecturas de big-endian porque los compiladores modernos no son estúpidos.
La desventaja es, por supuesto, que esta no es la misma interfaz estándar que htonl () y amigos (que no veo como una desventaja, porque el diseño de htonl () era una opción pobre).
Esto supone que está codificando en Linux utilizando un sistema operativo de 64 bits; la mayoría de los sistemas tienen htole(x)
o ntobe(x)
etc., estos son típicamente macro de los diversos bswap
''s
#include <endian.h>
#include <byteswap.h>
unsigned long long htonll(unsigned long long val)
{
if (__BYTE_ORDER == __BIG_ENDIAN) return (val);
else return __bswap_64(val);
}
unsigned long long ntohll(unsigned long long val)
{
if (__BYTE_ORDER == __BIG_ENDIAN) return (val);
else return __bswap_64(val);
}
Nota al margen; estas son solo funciones a las que llamar para cambiar el orden de bytes. Si está utilizando little endian, por ejemplo, con una gran red de endian, pero si usa una gran codificación de finalización, esto invertirá innecesariamente el orden de bytes, por lo que podría necesitar un pequeño if __BYTE_ORDER == __LITTLE_ENDIAN
" if __BYTE_ORDER == __LITTLE_ENDIAN
" para hacer que su código sea más portátil , dependiendo de tus necesidades
Actualización: editado para mostrar un ejemplo de cheque endian
Me gusta la respuesta sindical, bastante clara. Por lo general, cambio de bit para convertir entre little y big endian, aunque creo que la solución de unión tiene menos asignaciones y puede ser más rápida:
//note UINT64_C_LITERAL is a macro that appends the correct prefix
//for the literal on that platform
inline void endianFlip(unsigned long long& Value)
{
Value=
((Value & UINT64_C_LITERAL(0x00000000000000FF)) << 56) |
((Value & UINT64_C_LITERAL(0x000000000000FF00)) << 40) |
((Value & UINT64_C_LITERAL(0x0000000000FF0000)) << 24) |
((Value & UINT64_C_LITERAL(0x00000000FF000000)) << 8) |
((Value & UINT64_C_LITERAL(0x000000FF00000000)) >> 8) |
((Value & UINT64_C_LITERAL(0x0000FF0000000000)) >> 24) |
((Value & UINT64_C_LITERAL(0x00FF000000000000)) >> 40) |
((Value & UINT64_C_LITERAL(0xFF00000000000000)) >> 56);
}
Luego, para detectar si necesita hacer su flip sin macro magia, puede hacer algo similar a Pax, donde cuando se asigna un short a 0x0001 será 0x0100 en el sistema endian opuesto.
Asi que:
unsigned long long numberToSystemEndian
(
unsigned long long In,
unsigned short SourceEndian
)
{
if (SourceEndian != 1)
{
//from an opposite endian system
endianFlip(In);
}
return In;
}
Entonces, para usar esto, necesitaría que SourceEndian sea un indicador para comunicar la endianidad del número de entrada. Esto podría almacenarse en el archivo (si se trata de un problema de serialización) o comunicarse a través de la red (si se trata de un problema de serialización de la red).
Para detectar su endianidad, use la siguiente unión:
union {
unsigned long long ull;
char c[8];
} x;
x.ull = 0x0123456789abcdef; // may need special suffix for ULL.
Luego puede verificar el contenido de xc[]
para detectar dónde pasó cada byte.
Para hacer la conversión, usaría ese código de detección una vez para ver qué endianidad usa la plataforma, luego escribo mi propia función para hacer los intercambios.
Puede hacerlo dinámico para que el código se ejecute en cualquier plataforma (detectar una vez y luego usar un interruptor dentro de su código de conversión para elegir la conversión correcta) pero, si solo va a utilizar una plataforma, solo lo haría la detección una vez en un programa separado luego codifica una rutina de conversión simple, asegurándose de documentar que solo se ejecuta (o ha sido probado) en esa plataforma.
Aquí hay un código de muestra que preparé para ilustrarlo. Aunque ha sido probado, no de manera exhaustiva, debería ser suficiente para comenzar.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TYP_INIT 0
#define TYP_SMLE 1
#define TYP_BIGE 2
static unsigned long long cvt(unsigned long long src) {
static int typ = TYP_INIT;
unsigned char c;
union {
unsigned long long ull;
unsigned char c[8];
} x;
if (typ == TYP_INIT) {
x.ull = 0x01;
typ = (x.c[7] == 0x01) ? TYP_BIGE : TYP_SMLE;
}
if (typ == TYP_SMLE)
return src;
x.ull = src;
c = x.c[0]; x.c[0] = x.c[7]; x.c[7] = c;
c = x.c[1]; x.c[1] = x.c[6]; x.c[6] = c;
c = x.c[2]; x.c[2] = x.c[5]; x.c[5] = c;
c = x.c[3]; x.c[3] = x.c[4]; x.c[4] = c;
return x.ull;
}
int main (void) {
unsigned long long ull = 1;
ull = cvt (ull);
printf ("%llu/n",ull);
return 0;
}
Tenga en cuenta que esto solo comprueba si es grande / pequeño endian puro. Si tiene alguna variante extraña en la que los bytes se almacenan, por ejemplo, en orden {5,2,3,1,0,7,6,4}, cvt()
será un poco más complejo. Tal arquitectura no merece existir, pero no estoy descartando la locura de nuestros amigos en la industria de los microprocesadores :-)
También tenga en cuenta que este es un comportamiento técnicamente indefinido, ya que se supone que no debe acceder a un miembro del sindicato por ningún campo que no sea el último escrito. Probablemente funcionará con la mayoría de las implementaciones, pero, para el punto de vista del público purista, probablemente solo tenga que lidiar con el problema y usar macros para definir sus propias rutinas, algo así como:
// Assumes 64-bit unsigned long long.
unsigned long long switchOrderFn (unsigned long long in) {
in = (in && 0xff00000000000000ULL) >> 56
| (in && 0x00ff000000000000ULL) >> 40
| (in && 0x0000ff0000000000ULL) >> 24
| (in && 0x000000ff00000000ULL) >> 8
| (in && 0x00000000ff000000ULL) << 8
| (in && 0x0000000000ff0000ULL) << 24
| (in && 0x000000000000ff00ULL) << 40
| (in && 0x00000000000000ffULL) << 56;
return in;
}
#ifdef ULONG_IS_NET_ORDER
#define switchOrder(n) (n)
#else
#define switchOrder(n) switchOrderFn(n)
#endif
Qué tal si:
#define ntohll(x) ( ( (uint64_t)(ntohl( (uint32_t)((x << 32) >> 32) )) << 32) |
ntohl( ((uint32_t)(x >> 32)) ) )
#define htonll(x) ntohll(x)
Una manera fácil sería usar ntohl en las dos partes por separado:
unsigned long long htonll(unsigned long long v) {
union { unsigned long lv[2]; unsigned long long llv; } u;
u.lv[0] = htonl(v >> 32);
u.lv[1] = htonl(v & 0xFFFFFFFFULL);
return u.llv;
}
unsigned long long ntohll(unsigned long long v) {
union { unsigned long lv[2]; unsigned long long llv; } u;
u.llv = v;
return ((unsigned long long)ntohl(u.lv[0]) << 32) | (unsigned long long)ntohl(u.lv[1]);
}
Yo recomendaría leer esto: http://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
uint64_t
ntoh64(const uint64_t *input)
{
uint64_t rval;
uint8_t *data = (uint8_t *)&rval;
data[0] = *input >> 56;
data[1] = *input >> 48;
data[2] = *input >> 40;
data[3] = *input >> 32;
data[4] = *input >> 24;
data[5] = *input >> 16;
data[6] = *input >> 8;
data[7] = *input >> 0;
return rval;
}
uint64_t
hton64(const uint64_t *input)
{
return (ntoh64(input));
}
int
main(void)
{
uint64_t ull;
ull = 1;
printf("%"PRIu64"/n", ull);
ull = ntoh64(&ull);
printf("%"PRIu64"/n", ull);
ull = hton64(&ull);
printf("%"PRIu64"/n", ull);
return 0;
}
Mostrará el siguiente resultado:
1
72057594037927936
1
Puede probar esto con ntohl () si suelta los 4 bytes superiores.
También puede convertir esto en una buena función de plantilla en C ++ que funcionará en cualquier número entero:
template <typename T>
static inline T
hton_any(const T &input)
{
T output(0);
const std::size_t size = sizeof(input) - 1;
uint8_t *data = reinterpret_cast<uint8_t *>(&output);
for (std::size_t i = 0; i < size; i++) {
data[i] = input >> ((size - i) * 8);
}
return output;
}
¡Ahora tu 128 bit también es seguro!
algunos sistemas BSD tienen betoh64
que hace lo que necesita.
función universal para cualquier tamaño de valor.
template <typename T>
T swap_endian (T value)
{
union {
T src;
unsigned char dst[sizeof(T)];
} source, dest;
source.src = value;
for (size_t k = 0; k < sizeof(T); ++k)
dest.dst[k] = source.dst[sizeof(T) - k - 1];
return dest.src;
}
una línea macro para el intercambio de 64 bits en pequeñas máquinas endian.
#define bswap64(y) (((uint64_t)ntohl(y)) << 32 | ntohl(y>>32))
htonl
se puede hacer por debajo de los pasos
- Si su sistema Big Endian devuelve el valor directamente. No hay necesidad de hacer ninguna conversión. Si su sistema endian pequeño, necesita hacer la conversión a continuación.
- Tome LSB 32 bit y aplique ''htonl'' y cambie 32 veces.
- Tome MSB 32 bit (cambiando el valor uint64_t 32 veces a la derecha) y aplique ''htonl''
- Ahora aplique bit wise O para el valor recibido en el 2 ° y 3 ° paso.
Del mismo modo para ntohll
también
#define HTONLL(x) ((1==htonl(1)) ? (x) : (((uint64_t)htonl((x) & 0xFFFFFFFFUL)) << 32) | htonl((uint32_t)((x) >> 32)))
#define NTOHLL(x) ((1==ntohl(1)) ? (x) : (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32)))
Puede delcare por encima de 2 definiciones también como funciones.
template <typename T>
static T ntoh_any(T t)
{
static const unsigned char int_bytes[sizeof(int)] = {0xFF};
static const int msb_0xFF = 0xFF << (sizeof(int) - 1) * CHAR_BIT;
static bool host_is_big_endian = (*(reinterpret_cast<const int *>(int_bytes)) & msb_0xFF ) != 0;
if (host_is_big_endian) { return t; }
unsigned char * ptr = reinterpret_cast<unsigned char *>(&t);
std::reverse(ptr, ptr + sizeof(t) );
return t;
}
Funciona para 2 bytes, 4 bytes, 8 bytes y 16 bytes (si tiene un número entero de 128 bits). Debe ser OS / plataforma independiente.
uint32_t SwapShort(uint16_t a)
{
a = ((a & 0x00FF) << 8) | ((a & 0xFF00) >> 8);
return a;
}
uint32_t SwapWord(uint32_t a)
{
a = ((a & 0x000000FF) << 24) |
((a & 0x0000FF00) << 8) |
((a & 0x00FF0000) >> 8) |
((a & 0xFF000000) >> 24);
return a;
}
uint64_t SwapDWord(uint64_t a)
{
a = ((a & 0x00000000000000FFULL) << 56) |
((a & 0x000000000000FF00ULL) << 40) |
((a & 0x0000000000FF0000ULL) << 24) |
((a & 0x00000000FF000000ULL) << 8) |
((a & 0x000000FF00000000ULL) >> 8) |
((a & 0x0000FF0000000000ULL) >> 24) |
((a & 0x00FF000000000000ULL) >> 40) |
((a & 0xFF00000000000000ULL) >> 56);
return a;
}