2009-12-09 20 views
7

Necesito convertir un valor corto del orden de bytes del host a little endian. Si el objetivo era big endian, podría usar la función htons(), pero desafortunadamente no lo es.¿Cómo convierto un valor de orden de bytes de host a little endian?

supongo que podría hacer:

swap(htons(val)) 

pero esto podría potencialmente causar los bytes que se pueden intercambiar dos veces, haciendo que el resultado correcto, pero me da una penalización de rendimiento que no está bien en mi caso.

+0

no se preocupe por la pérdida de rendimiento. Si hace cosas redundantes (intercambiando un int dos veces) el compilador detectará y eliminará el código durante su fase de optimización. –

+0

Lo que dijo Nils, pero un enfoque más cuidadoso es verificar primero el código generado en el nivel de optimización que puede pagar (si tiene problemas con la depuración, entonces uh-oh). Si se optimiza el intercambio doble, su problema de rendimiento se resuelve al instante. – MaR

+0

¿El compilador realmente logrará optimizar esto? Supongo que si swap() y htons() son macros o funciones en línea, lo hará, pero de lo contrario? – anorm

Respuesta

4

algo como lo siguiente:

unsigned short swaps(unsigned short val) 
{ 
    return ((val & 0xff) << 8) | ((val & 0xff00) >> 8); 
} 

/* host to little endian */ 

#define PLATFORM_IS_BIG_ENDIAN 1 
#if PLATFORM_IS_LITTLE_ENDIAN 
unsigned short htoles(unsigned short val) 
{ 
    /* no-op on a little endian platform */ 
    return val; 
} 
#elif PLATFORM_IS_BIG_ENDIAN 
unsigned short htoles(unsigned short val) 
{ 
    /* need to swap bytes on a big endian platform */ 
    return swaps(val); 
} 
#else 
unsigned short htoles(unsigned short val) 
{ 
    /* the platform hasn't been properly configured for the */ 
    /* preprocessor to know if it's little or big endian */ 

    /* use potentially less-performant, but always works option */ 

    return swaps(htons(val)); 
} 
#endif 

Si usted tiene un sistema que está configurado correctamente (tal manera que el preprocesador sabe si la ID de destino poco o big endian) se obtiene una versión 'optimizado' de htoles(). De lo contrario, obtendrá la versión potencialmente no optimizada que depende de htons(). En cualquier caso, obtienes algo que funciona.

Nada demasiado complicado y más o menos portátil.

Por supuesto, puede mejorar aún más las posibilidades de optimización implementando esto con inline o como macros como mejor le parezca.

Es posible que desee ver algo así como el "arnés de fuente abierta portátil (POSH)" para una implementación real que define el endianness para varios compiladores. Tenga en cuenta que para acceder a la biblioteca es necesario pasar por una página de seudo autenticación (aunque no es necesario que se registre para dar detalles personales): http://hookatooka.com/poshlib/

+0

Justo lo que estaba buscando. Estoy en un entorno Linux/gcc, por lo que al incluir , __BYTE_ORDER se define como __LITTLE_ENDIAN o __BIG_ENDIAN (o __PDP_ENDIAN) – anorm

6

Entonces debe conocer su endianness y llamar a htons() condicionalmente. En realidad, ni siquiera htons, solo intercambian bytes condicionalmente. Tiempo de compilación, por supuesto.

+2

+1. Si estás sudando por el rendimiento, #ifdef es tu amigo aquí. –

0

Este truco debería: al inicio, use ntohs con un valor ficticio y luego compare el valor resultante con el valor original. Si ambos valores son los mismos, entonces la máquina usa big endian, de lo contrario es poco endian.

Luego, utilice un método ToLittleEndian que no hace nada o invoca ntohs, según el resultado de la prueba inicial.

(Editado con la información proporcionada en los comentarios)

+0

¿Notaste que a OP le preocupa la penalización de rendimiento? ;-) –

+0

OP solo necesita hacer esta comprobación una vez, al inicio. –

+1

flyfishr64, y verifique los resultados cada vez. Nunca posponga hasta el tiempo de ejecución lo que puede hacer en tiempo de compilación. –

7

Aquí es un artículo sobre endianness y cómo determinar que parte de IBM:

Writing endian-independent code in C: Don't let endianness "byte" you

Incluye un ejemplo de cómo determinar endianness en tiempo de ejecución (que solo necesitaría hacer una vez)

const int i = 1; 
#define is_bigendian() ((*(char*)&i) == 0) 

int main(void) { 
    int val; 
    char *ptr; 
    ptr = (char*) &val; 
    val = 0x12345678; 
    if (is_bigendian()) { 
     printf(“%X.%X.%X.%X\n", u.c[0], u.c[1], u.c[2], u.c[3]); 
    } else { 
     printf(“%X.%X.%X.%X\n", u.c[3], u.c[2], u.c[1], u.c[0]); 
    } 
    exit(0); 
} 

La página también tiene una sección sobre métodos para orden de bytes inverso:

short reverseShort (short s) { 
    unsigned char c1, c2; 

    if (is_bigendian()) { 
     return s; 
    } else { 
     c1 = s & 255; 
     c2 = (s >> 8) & 255; 

     return (c1 << 8) + c2; 
    } 
} 

;

short reverseShort (char *c) { 
    short s; 
    char *p = (char *)&s; 

    if (is_bigendian()) { 
     p[0] = c[0]; 
     p[1] = c[1]; 
    } else { 
     p[0] = c[1]; 
     p[1] = c[0]; 
    } 

    return s; 
} 
+1

¿Es la condición de comprobación mejor que realizar un intercambio extra? –

+0

El objetivo de esto es 1. hacer que el código sea portátil, y 2. solo hacer el intercambio en plataformas que ya no sean pequeñas. En el caso en que el sistema ya es pequeño endian, terminas con una prueba y salto vs. 4 byte swaps. –

+0

Otra cosa, dado que el condicional nunca cambia '#define is_bigendian() ((* (char *) & i) == 0)' , supongo que el pronosticador de rama en la CPU probablemente lo eliminará dando como resultado esto efectivamente convirtiéndose en un noop cuando el sistema ya es pequeño endian. –

0

Mi regla de dedo conjetura rendimiento es que eso depende de si usted es ascendente hacia la izquierda-ising un gran bloque de datos de una sola vez, o simplemente un valor:

Si hay un solo valor, entonces la sobrecarga de llamada de función probablemente arruinará la sobrecarga de byte-swaps innecesarios, y eso incluso si el compilador no optimiza los byte swaps innecesarios. Entonces, tal vez va a escribir el valor como el número de puerto de una conexión de socket, e intenta abrir o enlazar un socket, que toma una edad en comparación con cualquier tipo de manipulación de bits. Así que no te preocupes por eso.

Si es un bloque grande, entonces puede que le preocupe que el compilador no lo maneje. Haga algo como esto:

if (!is_little_endian()) { 
    for (int i = 0; i < size; ++i) { 
     vals[i] = swap_short(vals[i]); 
    } 
} 

O mire las instrucciones SIMD en su arquitectura que pueden hacerlo considerablemente más rápido.

Escriba is_little_endian() utilizando el truco que desee. Creo que el que proporciona Robert S. Barnes es sólido, pero como normalmente se sabe para un objetivo determinado, ya sea grande o pequeño, quizás se deba tener un archivo de encabezado específico de la plataforma, que lo define como un macro evaluando ya sea a 1 o 0.

Como siempre, si realmente le importa el rendimiento, mire el ensamblaje generado para ver si se eliminó el código inútil o no, y mida las diferentes alternativas entre sí para ver qué es lo que realmente funciona más rápido.

0

Desafortunadamente, no existe una forma cruzada de determinar el byte de un sistema ordene en tiempo de compilación con el estándar C. Sugiero que agregue un #define a su config.h (o cualquier otra cosa que usted o su sistema de compilación use para la configuración de compilación).

Una unidad de prueba para comprobar la correcta definición de LITTLE_ENDIAN o BIG_ENDIAN podría tener este aspecto:

#include <assert.h> 
#include <limits.h> 
#include <stdint.h> 

void check_bits_per_byte(void) 
{ assert(CHAR_BIT == 8); } 

void check_sizeof_uint32(void) 
{ assert(sizeof (uint32_t) == 4); } 

void check_byte_order(void) 
{ 
    static const union { unsigned char bytes[4]; uint32_t value; } byte_order = 
     { { 1, 2, 3, 4 } }; 

    static const uint32_t little_endian = 0x04030201ul; 
    static const uint32_t big_endian = 0x01020304ul; 

    #ifdef LITTLE_ENDIAN 
    assert(byte_order.value == little_endian); 
    #endif 

    #ifdef BIG_ENDIAN 
    assert(byte_order.value == big_endian); 
    #endif 

    #if !defined LITTLE_ENDIAN && !defined BIG_ENDIAN 
    assert(!"byte order unknown or unsupported"); 
    #endif 
} 

int main(void) 
{ 
    check_bits_per_byte(); 
    check_sizeof_uint32(); 
    check_byte_order(); 
} 
Cuestiones relacionadas