Pregunta

Tengo un archivo binario que se ha creado en una máquina Unix. Es sólo un montón de registros escritos, uno tras otro. El registro se define así:

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;

consigo un montón de datos, pero no es los datos que espero. Soy sospechoso que mi problema tiene que ver con la diferencia endian de las máquinas, por lo que he llegado a preguntar sobre eso.

Yo entiendo que múltiples bytes se almacenarán en ascendente hacia la izquierda en las ventanas y big-endian en un entorno UNIX, y lo entiendo. Durante dos bytes, en las ventanas 0x1234 0x3412 será en un sistema UNIX.

¿Afecta endianness el orden de bytes de la estructura en su conjunto, o de cada miembro individual de la estructura? ¿Qué enfoques iba a tomar para convertir una estructura creada en un sistema UNIX a uno que tiene los mismos datos en un sistema de ventanas? Cualquier enlace que son más en profundidad que el orden de bytes de un par de bytes serían grandes, también!

¿Fue útil?

Solución

Además de la endian, es necesario tener en cuenta las diferencias de relleno entre las dos plataformas. Sobre todo si tiene matrices de longitud de carbonización impares y valores de 16 bits, que pueden encontrarse con diferente número de bytes de relleno entre algunos elementos.

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

A continuación, una vez que haya cargado la estructura, sólo cambio cada elemento:

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

Otros consejos

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

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

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

No leer directamente en estructura de un archivo! El embalaje puede ser diferente, usted tiene que jugar con el paquete pragma o construcciones específicas del compilador similares. Demasiado poco fiable. Una gran cantidad de programadores se salga con esto, ya que su código no se compila en gran número de arquitecturas y sistemas, pero eso no quiere decir que sea algo bien hacerlo!

Una buena alternativa es leer el encabezado, lo que, en un búfer y analizar de tres a evitar la sobrecarga de E / S en las 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;
}

Esta 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 que el código más limpio para analizar la memoria intermedia; el puntero se actualiza automáticamente al analizar a partir de la entrada.

Del mismo modo, establecimiento de memoria podrían tener un ayudante, algo 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 de 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 fija de todos modos.

NO abstracto en esta 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 los hay. En realidad, no obligatorio para la interfaz per se.

Observe cómo la elección de endianess aquí se llevó a cabo en tiempo de compilación (ya que crear el objeto little_endian_reader), por lo que invocar el método virtual para gastos generales no particularmente buena razón, por lo que no iría con este enfoque. ; -)

En esta etapa no hay ninguna razón real para mantener la "estructura formato de archivo" en torno a tal cual, puede organizar los datos a su gusto y no necesariamente leer en cualquier estructura específica en absoluto; Después de todo, es sólo de datos. Cuando uno lee los archivos como imágenes, que en realidad no necesitan alrededor de la cabecera .. usted debe tener su contenedor de imagen que es igual para todos los tipos de archivos, por lo que el código para leer un formato específico sólo debe leer el archivo, interpretar y cambiar el formato del datos y almacenar la carga útil. =)

Es decir, ¿esto parezca complicado?

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

El código se parece bonito, y ser una muy baja sobrecarga! 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;

Esto puede ser ilegal en algunas arquitecturas, por lo que la optimización podría ser una mala idea, y utilizar más lenta, 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 nodo de "movimiento" en el código intermedio, nada más, lo cual es bastante eficiente. Si la alineación es un problema de la plena lectura turnos o secuencia podría obtener generada, outch, pero todavía no está mal. Comparar rama podría permitir la optimización, si la prueba las direcciones de LSB y ver si se puede utilizar la versión rápida o lenta del análisis sintáctico. Pero esto significaría pena para la prueba en cada lectura. Puede que no sea la pena el esfuerzo.

Ah, claro, que estamos leyendo los encabezados y las cosas, no creo que es un cuello de botella en muchas aplicaciones. Si algún códec está haciendo un Innerloop muy apretado, de nuevo, la lectura en un búfer temporal y decodificar a partir de ahí es bien asesorado. El mismo principio .. nadie lee el byte-a-tiempo desde el archivo al procesar 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 hace" es que los sistemas de ficheros hacen bloque lee y que los bytes provienen de la memoria de todos modos, es cierto, pero que va a través de una pila de llamadas de profundidad la cual es de gran sobrecarga para conseguir unos pocos bytes!

Sin embargo, escribir el código de programa de análisis de una vez y tiempos de uso trillón -.> Epic win

Lectura directamente en estructura de un archivo: DON'T hacer todo amigos!

Afecta a cada miembro de forma independiente, no toda la struct. También, que no afecta a cosas como matrices. Por ejemplo, sólo hace bytes en un int s almacenados en orden inverso.

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

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

Sin embargo, se debe tener cuidado de otro problema: aligmenent de los miembros en su estructura. Básicamente, usted debe comprobar si sizeof (ACTA) es el mismo en UNIX y en código de Windows. Los compiladores suelen proporcionar pragmas para definir el alineamiento desee (por ejemplo, #pragma pack).

También hay que considerar las diferencias de alineación entre los dos compiladores. Se permite a cada compilador para insertar relleno entre los miembros de una estructura más se ajuste 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 se trata de una copia binaria lo que el endian-dad de la arquitectura de código.

Esta es la razón por la mayoría de programas (que he visto (que deben ser plataforma neutral)) serializar los datos como una cadena de texto que puede ser fácilmente leído por los iostreams estándar.

Me gusta poner en práctica un método SwapBytes para cada tipo de datos que hay que intercambiar, de esta manera:

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 añadir una función a la estructura que necesita el canje, así:

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

A continuación, puede modificar el código que lee (o escribe) la 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 apoyar diferentes plataformas que sólo tiene que tener una implementación específica de cada plataforma de sobrecarga byteswap.

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;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top