2011-03-22 11 views
33

Si tengo un struct en C++, ¿no hay forma de leerlo/escribirlo de forma segura en un archivo compatible con multiplataforma/compilador?Relleno de estructura en C++

Porque si entiendo correctamente, cada compilador 'almohadillas' de manera diferente en función de la plataforma de destino.

+3

La eficiencia (rendimiento) obtenida al realizar la E/S binaria a menudo no justifica el dinero gastado en investigación, diseño, desarrollo y especialmente en la depuración y el mantenimiento. El código fuente debe ser simple de entender, pero no más simple. –

Respuesta

33

No. Eso no es posible. Es debido a la falta de estandarización de C++ en el nivel binario .

Don Box escrituras (citando de su libro Essential COM, capítulo COM como una mejor C++)

C++ y Portabilidad


Una vez que se tomó la decisión de distribuir una clase C++ como DLL, uno se enfrenta con uno de el fundamental debilidades de C++, es decir, lac k de estandarización en el nivel binario. Aunque la norma ISO/ANSI C++ Proyecto Documento de trabajo intentos de codificar la cual programas compilar y lo que los efectos semánticos de ejecutarlos se ser, no hace ningún intento de estandarizar modelo de ejecución binaria de C++. El primera vez este problema se convertirá en evidente es cuando un cliente intenta vincular contra la biblioteca de importación del FastString DLL desde un entorno C++ Desarrollos otra que la utilizada para construir el FastString DLL.

El relleno de Struct se realiza de forma diferente por diferentes compiladores. Incluso si usa el mismo compilador, la alineación del empaque para las estructuras puede ser diferente según el pragma pack que esté utilizando.

No sólo eso, si usted escribe dos estructuras cuyos miembros son exactamente misma, la única diferencia es que el orden en el que están declaradas es diferente, entonces el tamaño de cada estructura puede ser (y es a menudo) diferente.

Por ejemplo, ver esto,

struct A 
{ 
    char c; 
    char d; 
    int i; 
}; 

struct B 
{ 
    char c; 
    int i; 
    char d; 
}; 

int main() { 
     cout << sizeof(A) << endl; 
     cout << sizeof(B) << endl; 
} 

Compilarlo con gcc-4.3.4, y se obtiene este resultado:

8 
12 

Es decir, los tamaños son diferentes a pesar de que ambas estructuras tienen los mismos miembros!Código

en Ideone: http://ideone.com/HGGVl

La conclusión es que la norma no habla de cómo el relleno se debe hacer, por lo que los compiladores son libres de tomar cualquier decisión y le no puede asumir todos los compiladores hacen la misma decisión.

+3

Hay' __attribute __ (()) 'que utilizo para las estructuras de memoria compartida, así como para las que se usan para mapear datos de red. Afecta el rendimiento (vea http://digitalvampire.org/blog/index.php/2006/07/31/why-you-shouldnt-use-__attribute__packed/) pero es una característica útil para las estructuras relacionadas con la red. (No es un estándar por lo que yo sé, por lo que la respuesta sigue siendo cierta). – Pijusn

+0

No entiendo por qué el tamaño de struct A es 8 y no más. { char c; // que hay de esto? char d; // tamaño 1 + relleno de 3 int i; // tamaño 4 }; – Dchris

+3

@Dchris: el compilador probablemente tenga cuidado de asegurarse de que cada campo esté alineado en función de su propia alineación natural. cyd son un byte y, por lo tanto, están alineados sin importar dónde los pongas para las instrucciones de la CPU de un solo byte. Sin embargo, el int debe estar alineado en un límite de 4 bytes, que para llegar allí requiere dos bytes de relleno después de d. Esto te lleva a 8. – hoodaticus

2

Puede usar algo como boost::serialization.

6

No, no hay manera segura. Además del relleno, tienes que ocuparte de diferentes órdenes de bytes y diferentes tamaños de tipos integrados.

Debe definir un formato de archivo y convertir su estructura a ese formato. Las bibliotecas de serialización (por ejemplo, boost :: serialization, o el protocolo de Google) pueden ayudar con esto.

+1

"El tamaño de una estructura (o clase) puede no ser igual a la suma del tamaño de sus miembros". –

+0

@Thomas: Exactamente. Y eso es solo el comienzo de la diversión. – Erik

3

Cuenta larga corta, no. No existe una manera independiente de la plataforma, conforme a los estándares para lidiar con el relleno.

El relleno se llama "alineación" en la norma, y ​​se comienza a hablar de ello en el 3,9/5: tipos

objetos tienen alineación requisitos (3.9.1, 3.9.2). La alineación de un tipo de objeto completo es un valor entero definido por la implementación que representa un número de bytes; un objeto se asigna a una dirección que cumple con los requisitos de alineación de su tipo de objeto.

Pero continúa a partir de ahí y termina en muchos rincones oscuros del Estándar. La alineación es "definida por la implementación", lo que significa que puede ser diferente en los diferentes compiladores, o incluso entre los diferentes modelos de direcciones (es decir, 32 bits/64 bits) bajo el mismo compilador ,.

A menos que tenga requisitos de rendimiento realmente severos, puede considerar almacenar sus datos en un disco en un formato diferente, como cadenas de caracteres. Muchos protocolos de alto rendimiento envían todo usando cadenas cuando el formato natural puede ser otra cosa. Por ejemplo, una fuente de intercambio de baja latencia en la que trabajé recientemente envía fechas como cadenas con el formato siguiente: "20110321" y los tiempos se envían de manera similar: "141055.200". A pesar de que este feed de intercambio envía 5 millones de mensajes por segundo durante todo el día, todavía usan cadenas para todo porque de esa manera pueden evitar el endian-ness y otros problemas.

11

Si tiene la oportunidad de diseñar la estructura usted mismo, debería ser posible. La idea básica es que debe diseñarlo de modo que no haya necesidad de insertar bytes de relleno en él. el segundo truco es que debes manejar las diferencias en endianess.

Describiré cómo construir la estructura utilizando escalares, pero debería poder usar estructuras anidadas, siempre que aplique el mismo diseño para cada estructura incluida.

Primero, un hecho básico en C y C++ es que la alineación de un tipo no puede exceder el tamaño del tipo. Si fuera así, no sería posible asignar memoria usando malloc(N*sizeof(the_type)).

Disposición de la estructura, comenzando con los tipos más grandes.

struct 
{ 
    uint64_t alpha; 
    uint32_t beta; 
    uint32_t gamma; 
    uint8_t delta; 

A continuación, la almohadilla de la estructura de forma manual, por lo que al final va a coincidir con el tipo más grande:

uint8_t pad8[3]; // Match uint32_t 
    uint32_t pad32;  // Even number of uint32_t 
} 

El siguiente paso es decidir si la estructura se debe almacenar en poco o grande formato endian La mejor manera es "intercambiar" todo el elemento in situ antes de escribir o después de leer la estructura, si el formato de almacenamiento no coincide con la endialencia del sistema host.

+0

Esto suena interesante. ¿Pero puede obtener más en detalle: por qué lo ordena por tipo de longitud descendiendo y por qué lo rellenó para que tenga un número par de uint32_t? – Phil

+1

@Phil, un tipo básico, como 'uint32_t', puede (potencialmente) tener un requisito de alineación que coincida con su tamaño, en este caso cuatro bytes. Un compilador puede insertar relleno para lograr esto. Al hacerlo manualmente, no habrá necesidad de que el compilador haga esto, ya que la alineación siempre será correcta. El inconveniente es que en sistemas con requisitos de alineación menos estrictos, una estructura acolchada manualmente será más grande que una acolchada por el compilador. Puede hacer esto en orden ascendente o descendente, pero necesitará insertar más pads en el medio de la estructura si lo hace int en orden ascendente ... – Lindydancer

+1

... El relleno en el extremo de la estructura solo es necesario si planea usarlo en matrices. – Lindydancer

Cuestiones relacionadas