2010-02-21 18 views
7

Tengo una estructura de datos heredada de 672 bytes de longitud. Estas estructuras se almacenan en un archivo, de forma secuencial, y tengo que leerlos enAlineación de estructuras C++ y vectores STL

Mientras que puedo leerlos en uno por uno, que sería bueno para hacer esto:.

// I know in advance how many structs to read in 
vector<MyStruct> bunchOfStructs; 
bunchOfStructs.resize(numberOfStructs); 

ifstream ifs; 
ifs.open("file.dat"); 
if (ifs) { 
    ifs.read(&bunchOfStructs[0], sizeof(MyStruct) * numberOfStructs); 
} 

Este funciona, pero creo que solo funciona porque el tamaño de la estructura de datos es uniformemente divisible por el relleno de alineación de estructuras de mi compilador. Sospecho que se romperá en otro compilador o plataforma.

La alternativa sería utilizar un bucle for para leer en cada estructura uno a la vez.

La pregunta -> ¿Cuándo tengo que preocuparme por la alineación de datos? ¿La memoria asignada dinámicamente en un vector usa relleno o STL garantiza que los elementos son contiguos?

+0

¿Están esas estructuras escritas en el archivo por código heredado o lo controlas también? –

+0

Están escritos por código heredado. Incluso si pudiera cambiarlo, podría tener que leer archivos escritos por versiones anteriores de la aplicación. – Nate

Respuesta

4

La norma requiere que sea capaz de crear una matriz de un tipo de estructura. Cuando lo hace, se requiere que la matriz sea contigua. Eso significa que, independientemente del tamaño asignado para la estructura, tiene que ser uno que le permita crear una matriz de ellos. Para asegurarse de que, el compilador puede asignar espacio adicional dentro de la estructura, pero no puede requerir espacio adicional entre las estructuras.

El espacio para los datos en un vector es (normalmente) asignado con ::operator new (a través de una clase de Allocator), y ::operator new se requiere para asignar espacio que está correctamente alineado para almacenar cualquier tipo.

Puede suministrar su propio Asignador y/o sobrecargar ::operator new - pero si lo hace, su versión todavía debe cumplir con los mismos requisitos, por lo que no cambiará nada a este respecto.

En otras palabras, exactamente lo que quiere que se requiere para trabajar, siempre y cuando los datos en el archivo fue creado esencialmente de la misma forma en que está tratando de leerlo de nuevo. Si se creó en otra máquina o con un compilador diferente (o incluso el mismo compilador con diferentes indicadores) tiene una cantidad considerable de problemas potenciales: puede obtener diferencias en endianness, relleno en la estructura, etc.

Editar: Dado que usted no sabe si las estructuras se han escrito en el formato esperado por el compilador, que no sólo es necesario leer las estructuras uno a la vez - que realmente necesita para leer el los elementos en las estructuras uno a la vez, luego coloque cada uno en un struct temporal, y finalmente agregue el struct rellenado a su colección.

Afortunadamente, puede sobrecargar operator>> para automatizar la mayor parte de esto. No mejora la velocidad (por ejemplo), pero puede mantener su código más limpio:

struct whatever { 
    int x, y, z; 
    char stuff[672-3*sizeof(int)]; 

    friend std::istream &operator>>(std::istream &is, whatever &w) { 
     is >> w.x >> w.y >> w.z; 
     return is.read(w.stuff, sizeof(w.stuff); 
    } 
}; 

int main(int argc, char **argv) { 
    std::vector<whatever> data; 

    assert(argc>1); 

    std::ifstream infile(argv[1]); 

    std::copy(std::istream_iterator<whatever>(infile), 
       std::istream_iterator<whatever>(), 
       std::back_inserter(data)); 
    return 0; 
} 
+0

Perfecto. Sé que no hay relleno entre las estructuras en el disco, ni relleno * dentro * de las estructuras en el disco. Pero supongo que no tengo una forma portátil de saber si el compilador va a agregar relleno dentro de las estructuras en la memoria. Así que parece que necesito leer las cosas en uno por vez para estar seguro. – Nate

2

Para su archivo existente, su mejor opción es descubrir su formato de archivo, y para leer cada tipo de forma individual, leer y descartar los bytes de alineación.

Es mejor no hacer ninguna suposición con struct alignment.

Para guardar datos nuevos en un archivo, puede usar algo como boost serialization.

+0

Eso suena como la manera segura. Lento y tedioso, pero seguro. :-) Sé que no hay relleno en el formato en disco. – Nate

2

En su caso, debe preocuparse por la alineación siempre que pueda cambiar el diseño de su estructura. Hay dos opciones para hacer que su código sea más portátil.

Primero, la mayoría de los compiladores tienen atributos extendidos o directivas de preprocesador que le permitirán empacar la estructura en un espacio mínimo. Esta opción puede desalinear potencialmente algunos de los campos dentro de la estructura, lo que podría reducir el rendimiento, pero garantizará que esté distribuido de la misma manera en cualquier máquina para la que lo construya. Compruebe su compilador para su documentación sobre #pragma pack(). En GCC puede usar __attribute__((__packed__)).

En segundo lugar, puede agregar relleno explícito a su estructura. Esta opción le permite mantener las propiedades de rendimiento de la estructura original, pero no dejará clara la forma en que se estructura. Por ejemplo:

struct s { 
    u_int8_t field1; 
    u_int8_t pad0[3]; 
    u_int16_t field2; 
    u_int8_t pad1[2]; 
    u_int32_t field3; 
}; 
1

Más de alineación, debe preocuparse por endianness. El STL garantiza que el almacenamiento en un vector es lo mismo que una matriz, pero los campos enteros en la propia estructura se almacenarán en diferentes formatos entre digamos x86 y RISC.

En cuanto a la alineación, Google para #pragma pack(1).

0

Si va a escribir código orientado a objetos que requiere el conocimiento de los mecanismos internos de una clase, lo estás haciendo incorrecto. No debe suponer nada sobre el funcionamiento interno de la clase; solo debe suponer que los métodos y propiedades funcionan igual en cualquier plataforma/compilador.

Probablemente sea mejor que implemente una clase que emule la funcionalidad del vector (quizás subclasificando el vector). Actuando quizás como una implementación de "patrón de proxy", podría cargar solo aquellas estructuras a las que la persona que ha accedido haya accedido. Esto también te permitirá lidiar con cualquier problema endian al mismo tiempo. De esta manera debería hacer que funcione para cualquier plataforma o compilador.

Cuestiones relacionadas