¡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!
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