2009-05-13 16 views
15

Tengo un archivo binario que se creó en una máquina Unix. Es solo un montón de registros escritos uno tras otro. El registro se define así:¿Cómo convierto una estructura big-endian en una pequeña estructura endian?

struct RECORD { 
    UINT32 foo; 
    UINT32 bar; 
    CHAR fooword[11]; 
    CHAR barword[11]; 
    UNIT16 baz; 
} 

Estoy tratando de averiguar cómo iba a leer e interpretar estos datos en una máquina Windows. Tengo algo como esto:

fstream f; 
f.open("file.bin", ios::in | ios::binary); 

RECORD r; 

f.read((char*)&detail, sizeof(RECORD)); 

cout << "fooword = " << r.fooword << endl; 

Tengo un montón de datos, pero no son los datos que espero. Sospecho que mi problema tiene que ver con la diferencia Endian de las máquinas, así que he venido a preguntar sobre eso.

Entiendo que se almacenarán múltiples bytes en little-endian en windows y big-endian en un entorno unix, y lo entiendo. Para dos bytes, 0x1234 en Windows será 0x3412 en un sistema Unix.

¿El endianness afecta el orden de bytes de la estructura como un todo, o de cada miembro individual de la estructura? ¿Qué enfoques tomaría para convertir una estructura creada en un sistema Unix en una que tenga los mismos datos en un sistema Windows? ¡Cualquier enlace que tenga más profundidad que el orden de bytes de un par de bytes sería genial también!

+1

No ha preguntado acerca de ellos, pero otra cosa a considerar al trabajar con este tipo de código heredado es bitfields. El orden en que se empaquetan los campos de bit puede ser dependiente tanto del compilador como de la plataforma y no relacionado con la endianidad del procesador. – Dan

Respuesta

12

Así como el endian, es necesario tener en cuenta las diferencias de relleno entre las dos plataformas. Particularmente si tiene arrays de caracteres impares y valores de 16 bits, puede encontrar diferentes números de bytes de relleno entre algunos elementos.

Editar: si la estructura se escribió sin embalaje, entonces debería ser bastante sencillo. Algo así como el código (no probado) debe hacer el trabajo:

// Functions to swap the endian of 16 and 32 bit values 

inline void SwapEndian(UINT16 &val) 
{ 
    val = (val<<8) | (val>>8); 
} 

inline void SwapEndian(UINT32 &val) 
{ 
    val = (val<<24) | ((val<<8) & 0x00ff0000) | 
      ((val>>8) & 0x0000ff00) | (val>>24); 
} 

Luego, una vez que haya cargado la estructura, sólo cambio cada elemento:

SwapEndian(r.foo); 
SwapEndian(r.bar); 
SwapEndian(r.baz); 
+0

He especificado #pragma pack (push, 1). – scottm

+0

@Scotty, eso no lo va a ayudar si los datos que está leyendo ya tienen bytes sueltos en él. FWIW, esto realmente no debería suceder a menos que el desarrollador del programa estuviera escribiendo estructuras completas, lo cual es simplemente malo. Las estructuras siempre deben escribirse campo por campo, para situaciones exactamente como esta. – Duck

+0

@Duck, tengo la fuente de la definición de la estructura (pero no para leer o escribir) y también tiene pack = 1. – scottm

3

Afecta a cada miembro de forma independiente, no al conjunto struct. Además, no afecta a cosas como matrices. Por ejemplo, solo hace bytes en un int s almacenados en orden inverso.

PS. Dicho esto, podría haber una máquina con endianness extraño. Lo que acabo de decir se aplica a la mayoría de las máquinas usadas (x86, ARM, PowerPC, SPARC).

+0

"Además, no afecta a cosas como las matrices.": ¡Pero afecta a los miembros de matrices si son de tipos de datos numéricos o caracteres con tamaño> 1 byte! – mmmmmmmm

+1

@rstevens: Sí, absolutamente. Quiero decir que no afecta el orden de los elementos en una matriz.Cada miembro obviamente se trata como una sola variable. –

10

En realidad, endianness es una propiedad del hardware subyacente, no del sistema operativo.

La mejor solución es convertir a un estándar al escribir los datos - Google de "orden de bytes de red" y que debe encontrar los métodos para hacer esto.

Editar: aquí está el enlace: http://www.gnu.org/software/hello/manual/libc/Byte-Order.html

+1

No llego a decidir cómo escribir los datos, ese proceso ha estado en vigor durante 10 años y no está cambiando. – scottm

+1

En cuyo caso debe descubrir el mecanismo exacto que se utilizó y escribir sus propias rutinas para convertirlas (o buscarlas en línea). Sin embargo, tenga en cuenta que, si bien el escritor "no está cambiando", es mejor que nunca se mueva a otra arquitectura o cambiará, le guste o no. – kdgregory

1

Hay que corregir la endianess de cada miembro de más de un byte, de forma individual. Las cadenas no necesitan ser convertidas (fooword y barword), ya que pueden verse como secuencias de bytes.

Sin embargo, debe ocuparse de otro problema: alineación de los miembros en su estructura. Básicamente, debe verificar si sizeof (RECORD) es el mismo tanto en el código de Unix como en el de Windows. Los compiladores generalmente proporcionan pragmas para definir la alineación que desea (por ejemplo, # paquete de pragma).

1

También hay que considerar las diferencias de alineación entre el dos compiladoresCada compilador tiene permitido insertar relleno entre los miembros en una estructura que se adapte mejor a la arquitectura. Por lo que realmente necesita saber:

  • Cómo el prog UNIX escribe en el archivo
  • Si se trata de una copia binaria del objeto de la disposición exacta de la estructura.
  • Si es una copia binaria, cuál es la endianidad de la arquitectura de origen.

Esta es la razón por la que la mayoría de los programas (que he visto (que deben ser neutrales a la plataforma)) serializan los datos como una secuencia de texto que puede leerse fácilmente con el iostream estándar.

0

Algo como esto debería funcionar:

#include <algorithm> 

struct RECORD { 
    UINT32 foo; 
    UINT32 bar; 
    CHAR fooword[11]; 
    CHAR barword[11]; 
    UINT16 baz; 
} 

void ReverseBytes(void *start, int size) 
{ 
    char *beg = start; 
    char *end = beg + size; 

    std::reverse(beg, end); 
} 

int main() { 
    fstream f; 
    f.open("file.bin", ios::in | ios::binary); 

    // for each entry { 
    RECORD r; 
    f.read((char *)&r, sizeof(RECORD)); 
    ReverseBytes(r.foo, sizeof(UINT32)); 
    ReverseBytes(r.bar, sizeof(UINT32)); 
    ReverseBytes(r.baz, sizeof(UINT16) 
    // } 

    return 0; 
} 
1

me gusta poner en práctica un método SwapBytes para cada tipo de datos que hay que intercambiar, así:

inline u_int ByteSwap(u_int in) 
{ 
    u_int out; 
    char *indata = (char *)&in; 
    char *outdata = (char *)&out; 
    outdata[0] = indata[3] ; 
    outdata[3] = indata[0] ; 

    outdata[1] = indata[2] ; 
    outdata[2] = indata[1] ; 
    return out; 
} 

inline u_short ByteSwap(u_short in) 
{ 
    u_short out; 
    char *indata = (char *)&in; 
    char *outdata = (char *)&out; 
    outdata[0] = indata[1] ; 
    outdata[1] = indata[0] ; 
    return out; 
} 

Luego agregar una función a la estructura que necesita intercambio, como este:

struct RECORD { 
    UINT32 foo; 
    UINT32 bar; 
    CHAR fooword[11]; 
    CHAR barword[11]; 
    UNIT16 baz; 
    void SwapBytes() 
    { 
    foo = ByteSwap(foo); 
    bar = ByteSwap(bar); 
    baz = ByteSwap(baz); 
    } 
} 

Luego puede modificar su código que lee (o escribe) t se estructura como esta:

fstream f; 
f.open("file.bin", ios::in | ios::binary); 

RECORD r; 

f.read((char*)&detail, sizeof(RECORD)); 
r.SwapBytes(); 

cout << "fooword = " << r.fooword << endl; 

para soportar diferentes plataformas sólo tiene que tener una implementación específica de cada plataforma de sobrecarga byteswap.

4

¡No lea directamente en struct desde un archivo! El embalaje puede ser diferente, tienes que manipular el paquete pragma o compilaciones específicas del compilador. Demasiado poco confiable. Muchos programadores se salen con la suya ya que su código no está compilado en una gran cantidad de arquitecturas y sistemas, ¡pero eso no significa que esté bien hacerlo!

Un buen enfoque alternativo es leer el encabezado, lo que sea, en un búfer y analizar a partir de tres para evitar la sobrecarga de E/S en operaciones atómicas como leer un entero sin signo de 32 bits!

char buffer[32]; 
char* temp = buffer; 

f.read(buffer, 32); 

RECORD rec; 
rec.foo = parse_uint32(temp); temp += 4; 
rec.bar = parse_uint32(temp); temp += 4; 
memcpy(&rec.fooword, temp, 11); temp += 11; 
memcpy(%red.barword, temp, 11); temp += 11; 
rec.baz = parse_uint16(temp); temp += 2; 

La declaración de parse_uint32 se vería así:

uint32 parse_uint32(char* buffer) 
{ 
    uint32 x; 
    // ... 
    return x; 
} 

Ésta es una abstracción muy simple, que no cuesta nada extra en la práctica para actualizar el puntero así:

uint32 parse_uint32(char*& buffer) 
{ 
    uint32 x; 
    // ... 
    buffer += 4; 
    return x; 
} 

La forma posterior permite un código más limpio para analizar el búfer; el puntero se actualiza automáticamente cuando se analiza desde la entrada.

Del mismo modo, establecimiento de memoria podrían tener un ayudante, algo así como:

void parse_copy(void* dest, char*& buffer, size_t size) 
{ 
    memcpy(dest, buffer, size); 
    buffer += size; 
} 

La belleza de este tipo de disposición es que se puede tener espacio de nombres "LITTLE_ENDIAN" y "BIG_ENDIAN", entonces usted puede hacer esto en su código:

using little_endian; 
// do your parsing for little_endian input stream here.. 

fácil cambiar endianess para el mismo código, sin embargo, pocas veces se necesita la característica de archivos de los formatos .. por lo general tienen un endianess fijo de todos modos.

NO abstraiga esto en clase con métodos virtuales; se acaba de agregar una sobrecarga, pero no dude en si así lo desea:

little_endian_reader reader(data, size); 
uint32 x = reader.read_uint32(); 
uint32 y = reader.read_uint32(); 

El objeto lector, obviamente, sólo puede ser una envoltura delgada alrededor del puntero. El parámetro de tamaño sería para la comprobación de errores, si corresponde. No es realmente obligatorio para la interfaz per se.

Observe cómo la elección de endianess aquí se hizo en COMPILATION TIME (ya que creamos little_endian_reader object), por lo que invocamos la sobrecarga del método virtual sin una razón particularmente buena, así que no seguiría con este enfoque. ;-)

En esta etapa no hay una razón real para mantener la "estructura del formato de archivo" tal como está, puedes organizar los datos a tu gusto y no necesariamente leerlos en ninguna estructura específica; después de todo, solo son datos. Cuando lee archivos como imágenes, realmente no necesita el encabezado ... debería tener su contenedor de imágenes que es el mismo para todos los tipos de archivos, por lo que el código para leer un formato específico debería simplemente leer el archivo, interpretar y reformatear el datos & almacenar la carga útil. =)

Quiero decir, ¿esto parece complicado?

uint32 xsize = buffer.read<uint32>(); 
uint32 ysize = buffer.read<uint32>(); 
float aspect = buffer.read<float>();  

¡El código puede verse tan bien, y tener un bajo consumo de energía! Si el endianess es el mismo para el archivo y la arquitectura del código se compila para el Innerloop puede tener este aspecto:

uint32 value = *reinterpret_cast<uint32*>)(ptr); ptr += 4; 
return value; 

que podría ser ilegal en algunas arquitecturas, por lo que la optimización podría ser una mala idea, y utilizar más lento, pero el enfoque más robusto:

uint32 value = ptr[0] | (static_cast<uint32>(ptr[1]) << 8) | ...; ptr += 4; 
return value; 

en un x86 que puede compilar en bswap o mov, que es razonablemente baja sobrecarga si se inlined el método; el compilador insertaría el nodo "mover" en el código intermedio, nada más, que es bastante eficiente. Si la alineación es un problema, la secuencia completa de lectura-cambio-o puede ser generada, outch, pero aún así no muy raída. Compare-branch podría permitir la optimización, si prueba los LSB de la dirección y ve si puede usar la versión rápida o lenta del análisis sintáctico. Pero esto significaría una penalización por la prueba en cada lectura. Puede que no valga la pena el esfuerzo.

Oh, bien, estamos leyendo ENCABEZADOS y esas cosas, no creo que sea un cuello de botella en demasiadas aplicaciones. Si algún códec está haciendo realmente un VELOCIDAD interno muy apretado, de nuevo, leer en un búfer temporal y decodificar desde allí está bien asesorado. Mismo principio ... nadie lee byte-at-time del archivo cuando procesa un gran volumen de datos. Bueno, en realidad, he visto ese tipo de código muy a menudo y la respuesta habitual a "por qué lo haces" es que los sistemas de archivos bloquean las lecturas y que los bytes provienen de la memoria de todos modos, cierto, pero pasan por una pila de llamadas profunda que es alta sobrecarga para obtener unos pocos bytes!

Aún así, escriba el código del analizador una vez y use un trillón de veces -> victoria épica.

Leyendo directamente en struct desde un archivo: ¡NO LO HAGA!

Cuestiones relacionadas