Relleno de estructura en C++
Si tengo un archivo struct
en C++, ¿no hay forma de leerlo/escribirlo de forma segura en un archivo que sea compatible con compiladores/plataformas cruzadas?
Porque si entendí correctamente, cada compilador se 'rellena' de manera diferente según la plataforma de destino.
No, eso no es posible. Es por la falta de estandarización de C++ a nivel binario .
Don Box escribe (citando su libro Essential COM , capítulo COM As A Better C++ )
C++ y portabilidad
Una vez que se toma la decisión de distribuir una clase C++ como DLL, uno se enfrenta a una de las debilidades fundamentales de C++ , es decir, la falta de estandarización a nivel binario . Aunque el borrador del documento de trabajo ISO/ANSI C++ intenta codificar qué programas se compilarán y cuáles serán los efectos semánticos de ejecutarlos, no intenta estandarizar el modelo de tiempo de ejecución binario de C++ . La primera vez que este problema se hará evidente es cuando un cliente intenta vincularse con la biblioteca de importación de FastString DLL desde un entorno de desarrollo de C++ distinto al utilizado para crear FastString DLL.
El relleno de estructuras se realiza de forma diferente según los diferentes compiladores. Incluso si usa el mismo compilador, la alineación del empaquetado para las estructuras puede ser diferente según el paquete pragma que esté usando.
No solo eso, si escribes dos estructuras cuyos miembros son exactamente iguales, la única diferencia es que el orden en el que se declaran es diferente, entonces el tamaño de cada estructura puede ser (y a menudo es) diferente.
Por ejemplo, vea 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;
}
Compílelo con gcc-4.3.4
y obtendrá este resultado:
8
12
Es decir, los tamaños son diferentes aunque ambas estructuras tengan los mismos miembros.
La conclusión es que el estándar no habla sobre cómo se debe hacer el relleno, por lo que los compiladores son libres de tomar cualquier decisión y no se puede asumir que todos los compiladores toman la misma decisión.
Si tiene la oportunidad de diseñar la estructura usted mismo, debería ser posible. La idea básica es que debes diseñarlo de manera que no sea necesario insertar bytes de pad en él. el segundo truco es que debes manejar las diferencias en endianess.
Describiré cómo construir la estructura usando escalares, pero deberías poder usar estructuras anidadas, siempre y cuando apliques 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í, entonces no sería posible asignar memoria usando malloc(N*sizeof(the_type))
.
Diseñe 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, complete la estructura manualmente, de modo que al final coincida 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 debe almacenarse en formato little o big endian. La mejor manera es "intercambiar" todos los elementos in situ antes de escribir o después de leer la estructura, si el formato de almacenamiento no coincide con el endianess del sistema host.
No, no hay manera segura. Además del relleno, hay que lidiar con diferentes ordenamientos de bytes y diferentes tamaños de tipos integrados.
Debe definir un formato de archivo y convertir su estructura hacia y desde ese formato. Las bibliotecas de serialización (por ejemplo, boost::serialization o los buffers de protocolo de Google) pueden ayudar con esto.
En pocas palabras, no. No existe una forma independiente de la plataforma y compatible con el estándar de lidiar con el relleno.
El relleno se llama "alineación" en el Estándar y comienza a discutirse en 3.9/5:
Los tipos de objetos tienen requisitos de alineación (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 una cantidad de bytes; un objeto se asigna en una dirección que cumple con los requisitos de alineación de su tipo de objeto.
Pero a partir de ahí continúa y termina en muchos rincones oscuros del Standard. La alineación está "definida por la implementación", lo que significa que puede ser diferente entre diferentes compiladores, o incluso entre modelos de direcciones (es decir, 32 bits/64 bits) bajo el mismo compilador.
A menos que tenga requisitos de rendimiento realmente estrictos, podría considerar almacenar sus datos en un disco en un formato diferente, como cadenas de caracteres. Muchos protocolos de alto rendimiento envían todo utilizando cadenas cuando el formato natural podría ser otro. Por ejemplo, un feed de intercambio de baja latencia en el que trabajé recientemente envía fechas como cadenas con el formato siguiente: "20110321" y las horas se envían de manera similar: "141055.200". Aunque 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 endianismo y otros problemas.