2012-03-24 14 views
5

Estoy tratando de leer datos de un archivo binario y ponerlo en una estructura. Los primeros bytes de data.bin son:Leyendo archivo en una estructura (C++)

03 56 04 FF FF FF ... 

Y mi aplicación es:

#include <iostream> 
#include <fstream> 

int main() 
{ 
    struct header { 
     unsigned char type; 
     unsigned short size; 
    } fileHeader; 

    std::ifstream file ("data.bin", std::ios::binary); 
    file.read ((char*) &fileHeader, sizeof header); 

    std::cout << "type: " << (int)fileHeader.type; 
    std::cout << ", size: " << fileHeader.size << std::endl; 

} 

La salida me esperaba es type: 3, size: 1110, pero por alguna razón que es type: 3, size: 65284, así que básicamente el segundo byte en el archivo se omite. ¿Que esta pasando aqui?

+1

¿Cuál es 'sizeof (cabecera)'? Estoy dispuesto a apostar que es '4' ... – Cameron

+0

Je, sí. Debería haber verificado eso. – vind

Respuesta

7

En realidad, el comportamiento está definido por la implementación. Lo que realmente sucede en su caso probablemente sea, hay un relleno de 1 byte, después de type miembro de la estructura, y luego sigue el segundo miembro size. Basé este argumento después de ver el resultado.

Aquí es su bytes de entrada:

03 56 04 FF FF FF 

el primer byte 03 va al primer byte de la estructura, que es type, y ves este 3 como salida. Luego, el siguiente byte 56 va al segundo byte que es el relleno ignorado, luego los siguientes dos bytes 04 FF van a los siguientes dos bytes de la estructura que es size (que es del tamaño 2 bytes). En la máquina little-endian, 04 FF se interpreta como 0xFF04 que no es más que 66284 que se obtiene como salida.

Y necesita básicamente una estructura compacta para apretar el relleno. Use #pragma paquete. Pero tal estructura sería lenta en comparación con la estructura normal. Una mejor opción es llenar la estructura de forma manual como:

char bytes[3]; 
std::ifstream file ("data.bin", std::ios::binary); 
file.read (bytes, sizeof bytes); //read first 3 bytes 

//then manually fill the header 
fileHeader.type = bytes[0]; 
fileHeader.size = ((unsigned short) bytes[2] << 8) | bytes[1]; 

Otra manera de escribir la última línea es la siguiente:

fileHeader.size = *reinterpret_cast<unsigned short*>(bytes+1); 

Pero esto es definido por la implementación, ya que depende de la endian-ness de la máquina. En la máquina little-endian, lo más probable es que funcione.

Un acercamiento amistoso sería esto (definido por la implementación):

std::ifstream file ("data.bin", std::ios::binary); 
file.read (&fileHeader.type, sizeof fileHeader.type); 
file.read (reinterpret_cast<char*>(&fileHeader.size), sizeof fileHeader.size); 

Pero, de nuevo, la última línea depende de la endian-dad de la máquina.

+0

El relleno estaría en la estructura, no en el archivo. Pruebe el paquete pragma: http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=VS.100%29.aspx –

+1

¡Genial, gracias! Ahora lo entiendo. 'paquete #pragma (1)' hizo el trabajo. – vind

+1

Gracias de nuevo por esta explicación tan detallada. Ahora llené cada miembro de la estructura por sí mismo como me recomendó. Eso parece funcionar mejor. – vind

1

Bueno, podría ser relleno estructural. Para hacer que las estructuras funcionen rápidamente en arquitecturas modernas, algunos compiladores colocarán relleno para mantenerlos alineados en límites de 4 u 8 bytes.

Puede anular esto con un pragma o una configuración de compilador. p.ej. Visual Studio es /Zp

Si esto sucediera, entonces vería el valor 56 en el primer carácter, luego leería los siguientes n bytes en el relleno, y luego leería los siguientes 2 en el espacio corto. Si el segundo byte se perdió como relleno, los siguientes 2 bytes se leerán en el corto. Y como el short ahora contiene los datos '04 FF ', esto (en little endian) equivale a 0xff04 que es 65284.

0

El compilador de compiladores estructura a bytes que son múltiplos de 2 o 4 para facilitar el acceso a ellos en código de máquina. No usaría el paquete #pragma a menos que fuera realmente necesario, y eso generalmente solo se aplica cuando se trabaja a un nivel realmente bajo (como el nivel de firmware). Wikipedia article on that.

Eso sucede porque los microprocesadores tienen operantions específicas de acceso a memoria en direcciones que son múltiplos de cuatro o dos, y eso hace que el código fuente más fácil de hacer, se utiliza la memoria más eficiente, ya veces el código es un poco Más rápido. Hay formas de detener ese comportamiento, por supuesto, como la directiva pragma pack, pero dependen de la compilación. Pero anular los valores predeterminados del compilador suele ser una mala idea, los chicos del compilador tenían una muy buena razón para hacerlo funcionar de esa manera.

Una solución mejor, para mí, sería resolver eso con C pura, que es muy, muy simple, y seguiría una buena práctica de programación, a saber: nunca confíe en lo que el compilador está haciendo con sus datos a bajo nivel.

Sé que simplemente hacer #pragma pack (1) es sexy, simple y nos da la sensación de que estamos tratando y comprendiendo directamente lo que está sucediendo en las entrañas de la computadora, y eso enciende a todos los programadores reales, pero la mejor solución es siempre la que se implementa con el idioma que está utilizando. Es más fácil de entender y, por lo tanto, más fácil de mantener; que es el comportamiento por defecto, así que debería funcionar en todas partes, y, en este caso específico, la solución C es realmente sencillo y straightfoward: acaba de leer su atributo estructura por atributos, como esto:

void readStruct(header &h, std::ifstream file) 
{ 
    file.read((char*) &h.type, sizeof(char)); 
    file.read((char *) &h.size, sizeof(short)); 
} 

(esto funcionará si define la estructura global, por supuesto)

Mejor aún, ya que estás trabajando con C++, sería definir un método miembro para hacer esa lectura por ti, y luego simplemente llama al myObject.readData(file). ¿Puedes ver la belleza y la simplicidad?

Más fácil de leer, mantener, compilar, conduce a un código más rápido y optimizado, es predeterminado.

Normalmente no me gusta meterme con las directivas de #pragma a menos que esté bastante seguro de lo que estoy haciendo. Las implicaciones pueden ser sorprendentes.

+0

¿Funcionará eso en la máquina big-endian? – Nawaz

+0

Si el programa que produjo el archivo se ejecutó en una máquina big-endian, sí. Si tiene que escribir código para trabajar con un archivo little-endian en una máquina big-endian, tendrá que manejarlo explícitamente. – Castilho

+0

lo que significa que no funcionará en todas partes, contrariamente a su afirmación * "por lo que debería funcionar en todas partes" *, y ciertamente no es el mejor enfoque. He proporcionado varios enfoques y también he mencionado los problemas con cada uno, en caso de haberlos. – Nawaz

0

se puede utilizar una directiva #pragma pack compilador para anular el tema de relleno:

#pragma pack(push) 
#pragma pack(1) 
struct header { 
    unsigned char type; 
    unsigned short size; 
} fileHeader; 
#pragma pack(pop)