c padding

¿Es la estructura de embalaje determinista?



padding (8)

¿Tendrán relleno idéntico entre variables?

En la práctica, a la mayoría les gusta tener el mismo diseño de memoria.

En teoría, dado que el estándar no dice mucho sobre cómo se debe emplear el relleno en los objetos, realmente no se puede asumir nada en el relleno entre los elementos.

Además, no puedo ver siquiera por qué querría saber / asumir algo sobre el relleno entre los miembros de una estructura. simplemente escriba el código C estándar, y estará bien.

Por ejemplo, digamos que tengo dos estructuras equivalentes b en diferentes proyectos:

typedef struct _a { int a; double b; char c; } a; typedef struct _b { int d; double e; char f; } b;

Suponiendo que no he usado ninguna directiva como #pragma pack y estas estructuras se compilan en el mismo compilador en la misma arquitectura, ¿tendrán un relleno idéntico entre variables?


Cualquier compilador en particular debe ser determinista, pero entre dos compiladores, o incluso el mismo compilador con diferentes opciones de compilación, o incluso entre diferentes versiones del mismo compilador, todas las apuestas están desactivadas.

Usted está mucho mejor si no depende de los detalles de la estructura, o si lo hace, debe insertar un código para verificar en el tiempo de ejecución que la estructura es realmente como depende.

Un buen ejemplo de esto es el cambio reciente de las arquitecturas de 32 a 64 bits, donde incluso si no cambió el tamaño de los enteros utilizados en una estructura, el empaquetamiento predeterminado de los enteros parciales cambió; donde anteriormente 3 paquetes de 32 bits en una fila se empaquetarían perfectamente, ahora se empaquetan en dos ranuras de 64 bits.

No es posible anticipar qué cambios pueden ocurrir en el futuro; Si depende de detalles que no están garantizados por el idioma, como el empaquetado de la estructura, debe verificar sus suposiciones en el tiempo de ejecución.


Cualquier compilador sano producirá un diseño de memoria idéntico para las dos estructuras. Los compiladores usualmente se escriben como programas perfectamente deterministas. El no determinismo debería agregarse explícita y deliberadamente, y por mi parte no veo el beneficio de hacerlo.

Sin embargo, eso no le permite convertir una struct _a* a una struct _b* y acceder a sus datos a través de ambos. Afaik, esto seguiría siendo una violación de las reglas estrictas de alias incluso si el diseño de la memoria es idéntico, ya que permitiría al compilador reordenar los accesos a través de la struct _a* con accesos a través de la struct _b* , lo que daría como resultado un comportamiento impredecible e indefinido. .


El compilador es determinista; Si no lo fuera, la compilación separada sería imposible. Dos unidades de traducción diferentes con la misma declaración de struct trabajarán juntas; Eso está garantizado por §6.2.7 / 1: Tipos compatibles y tipos compuestos .

Además, dos compiladores diferentes en la misma plataforma deberían interoperar, aunque esto no está garantizado por la norma. (Es un problema de calidad de implementación). Para permitir la interoperabilidad, los escritores de compiladores acuerdan una plataforma ABI (Interfaz binaria de aplicación) que incluirá una especificación precisa de cómo se representan los tipos compuestos. De esta manera, es posible que un programa compilado con un compilador utilice módulos de biblioteca compilados con un compilador diferente.

Pero no solo estás interesado en el determinismo; También desea que el diseño de dos tipos diferentes sea el mismo.

Según la norma, dos tipos de struct son compatibles si sus miembros (tomados en orden) son compatibles, y si sus etiquetas y nombres de miembros son los mismos. Dado que las structs ejemplo tienen diferentes etiquetas y nombres, no son compatibles aunque sus tipos de miembros lo sean, por lo que no puede usar una donde se requiere la otra.

Puede parecer extraño que el estándar permita que las etiquetas y los nombres de los miembros afecten la compatibilidad. La norma requiere que los miembros de una estructura se establezcan en orden de declaración, por lo que los nombres no pueden cambiar el orden de los miembros dentro de la estructura. ¿Por qué, entonces, podrían afectar el relleno? No conozco ningún compilador donde lo hagan, pero la flexibilidad de la norma se basa en el principio de que los requisitos deben ser los mínimos necesarios para garantizar la ejecución correcta. El alias de estructuras etiquetadas de manera diferente no está permitido dentro de una unidad de traducción, por lo que no es necesario aprobarlas entre diferentes unidades de traducción. Y así, la norma no lo permite. (Sería legítimo para una implementación insertar información sobre el tipo en los bytes de relleno de una struct , incluso si fuera necesario agregar de manera determinista el relleno para proporcionar espacio para dicha información. La única restricción es que el relleno no se puede colocar antes del primer miembro de una struct )

Es probable que una plataforma ABI especifique el diseño de un tipo compuesto sin referencia a su etiqueta o nombre de miembro. En una plataforma en particular, con una plataforma ABI que tiene una especificación de este tipo y un compilador documentado para ajustarse a la plataforma ABI, usted podría salirse con el alias, aunque no sería técnicamente correcto, y obviamente las condiciones previas lo hacen no portátil. .


El estándar C en sí mismo no dice nada al respecto, por lo que, en principio, no puede estar seguro.

Pero : lo más probable es que su compilador se adhiera a alguna ABI en particular, de lo contrario, la comunicación con otras bibliotecas y con el sistema operativo sería una pesadilla. En este último caso, el ABI generalmente prescribirá exactamente cómo funciona el empaque.

Por ejemplo:

  • en x86_64 Linux / BSD, la ABI de SystemV AMD64 es la referencia. Aquí (§3.1) para cada tipo de datos del procesador primitivo se detalla la correspondencia con el tipo C, su tamaño y su requisito de alineación, y se explica cómo usar estos datos para completar el diseño de memoria de los campos de bits, las estructuras y las uniones; Todo (además del contenido real del relleno) es específico y determinista . Lo mismo ocurre con muchas otras arquitecturas, consulte estos enlaces .

  • ARM recomienda su EABI para sus procesadores, y generalmente es seguida por Linux y Windows; la alineación de agregados se especifica en "Procedimiento Estándar de Llamada para la Documentación de Arquitectura ARM", §4.3.

  • en Windows no hay un estándar de proveedor cruzado, pero VC ++ esencialmente dicta el ABI, al cual se adhiere virtualmente cualquier compilador; se puede encontrar here para x86_64, here para ARM (pero para la parte de interés de esta pregunta solo se refiere al ARM EABI).


ISO C dice que dos tipos de struct en diferentes unidades de traducción son compatibles si tienen la misma etiqueta y los mismos miembros. Más precisamente, aquí está el texto exacto del estándar C99:

6.2.7 Tipo compatible y tipo compuesto

Dos tipos tienen tipo compatible si sus tipos son iguales. Las reglas adicionales para determinar si dos tipos son compatibles se describen en 6.7.2 para especificadores de tipo, en 6.7.3 para calificadores de tipo y en 6.7.5 para declaradores. Además, dos tipos de estructura, unión o enumerados declarados en unidades de traducción separadas son compatibles si sus etiquetas y miembros cumplen con los siguientes requisitos: Si se declara una con una etiqueta, la otra se debe declarar con la misma etiqueta. Si ambos son tipos completos, entonces se aplican los siguientes requisitos adicionales: debe haber una correspondencia uno a uno entre sus miembros, de modo que cada par de miembros correspondientes se declaren con tipos compatibles, y de tal manera que si un miembro de un par correspondiente es declarado con un nombre, el otro miembro se declara con el mismo nombre. Para dos estructuras, los miembros correspondientes serán declarados en el mismo orden. Para dos estructuras o uniones, los campos de bits correspondientes tendrán los mismos anchos. Para dos enumeraciones, los miembros correspondientes tendrán los mismos valores.

Parece muy extraño si lo interpretamos desde el punto de vista de "¿qué, la etiqueta o los nombres de los miembros podrían afectar el relleno?" Pero, básicamente, las reglas son tan estrictas como pueden ser al mismo tiempo que permiten el caso común: varias unidades de traducción que comparten el texto exacto de una declaración de estructura a través de un archivo de encabezado. Si los programas siguen reglas más flexibles, no están equivocados; simplemente no se basan en los requisitos de comportamiento de la norma, sino en otros.

En su ejemplo, usted está en conflicto con las reglas de lenguaje, al tener solo equivalencia estructural, pero no etiquetas y nombres de miembros equivalentes. En la práctica, esto no se aplica realmente; Los tipos de estructura con diferentes etiquetas y nombres de miembros en diferentes unidades de traducción son de hecho compatibles físicamente de todos modos. Todo tipo de tecnología depende de esto, como los enlaces de lenguajes que no son C a bibliotecas C.

Si sus dos proyectos están en C (o C ++), probablemente valdría la pena intentar poner la definición en un encabezado común.

También es una buena idea poner algo de defensa contra los problemas de versiones, como un campo de tamaño:

// Widely shared definition between projects affecting interop! // Do not change any of the members. // Add new ones only at the end! typedef struct a { size_t size; // of whole structure int a; double b; char c; } a;

La idea es que quienquiera que construya una instancia de a debe inicializar el campo de size a sizeof (a) . Luego, cuando el objeto pasa a otro componente de software (quizás del otro proyecto), puede comparar el tamaño con su tamaño de sizeof (a) . Si el campo de tamaño es más pequeño, entonces sabe que el software que construyó a está utilizando una declaración antigua con menos miembros. Por lo tanto, los miembros inexistentes no deben ser accedidos.


No se puede abordar de manera determinista el diseño de una estructura o unión en lenguaje C en diferentes sistemas.

Si bien muchas veces puede parecer que el diseño generado por los diferentes compiladores es el mismo, debe considerar los casos como una convergencia dictada por la conveniencia práctica y funcional del diseño del compilador en el ámbito de la libertad de elección que el programador le otorga al programador, y por lo tanto no. eficaz.

La norma C11 ISO / IEC 9899: 2011 , casi sin cambios respecto a las normas anteriores, se establece claramente en el párrafo 6.7.2.1 Especificadores de estructura y unión :

Cada miembro que no pertenece a un campo de bits de una estructura u objeto de unión se alinea de una manera definida por la implementación apropiada para su tipo.

Lo peor es el caso de los campos de bits donde se deja una gran autonomía al programador:

Una implementación puede asignar cualquier unidad de almacenamiento direccionable lo suficientemente grande como para contener un campo de bits. Si queda suficiente espacio, un campo de bits que sigue inmediatamente a otro campo de bits en una estructura se empaquetará en bits adyacentes de la misma unidad. Si no queda espacio suficiente, si un campo de bits que no se ajusta se coloca en la siguiente unidad o se superpone a las unidades adyacentes se define por la implementación. El orden de asignación de los campos de bits dentro de una unidad (orden alto a orden bajo o orden bajo a orden alto) está definido por la implementación. La alineación de la unidad de almacenamiento direccionable no está especificada.

Solo cuente cuántas veces aparecen en el texto los términos "definido por la implementación" y "no especificado".

Acordó que no es asequible verificar la versión del compilador, la máquina y la arquitectura de destino en cada ejecución anterior para usar la estructura o unión generada en un sistema diferente, por lo que debería haber obtenido una respuesta decente a su pregunta.

Ahora digamos que sí, hay una forma de circular.

Tenga en claro que no es definitivamente la solución , pero es un enfoque común que puede encontrar cuando el intercambio de estructuras de datos se comparte entre diferentes sistemas: elementos de la estructura del paquete en el valor 1 (tamaño de caracteres estándar).

El uso del empaque y una definición precisa de la estructura pueden llevar a una declaración suficientemente confiable que se puede usar en diferentes sistemas. El empaquetamiento obliga al compilador a eliminar las alineaciones definidas por la implementación, reduciendo las eventuales incompatibilidades debidas al estándar. Además, al evitar utilizar campos de bits, puede eliminar inconsistencias dependientes de la implementación residual. Por último, la eficiencia de acceso, debido a la falta de alineación, puede recrearse agregando manualmente alguna declaración ficticia entre los elementos, diseñada de tal manera que haga retroceder cada campo en la alineación correcta.

Como caso residual, debe considerar un relleno en el extremo de la estructura que algunos compiladores agregan, pero debido a que no hay datos útiles asociados, puede ignorarlo (a menos que se trate de una asignación dinámica de espacio, pero nuevamente puede lidiar con eso).


Sí. Siempre debes asumir un comportamiento determinista de tu compilador.

[EDITAR] De los comentarios a continuación, es obvio que hay muchos programadores de Java que leen la pregunta anterior. Seamos claros: las estructuras C no generan ningún nombre, hash ni me gusta en los archivos de objetos, bibliotecas o archivos DLL. Las firmas de la función C tampoco se refieren a ellas. Lo que significa que los nombres de los miembros se pueden cambiar a su antojo, ¡en serio! - siempre que el tipo y orden de las variables miembro sea el mismo. En C, las dos estructuras en el ejemplo son equivalentes, ya que el empaque no cambia. lo que significa que el siguiente abuso es perfectamente válido en C, y ciertamente hay un abuso mucho peor en algunas de las bibliotecas más utilizadas.

[EDIT2] Nadie debería atreverse a hacer nada de lo siguiente en C ++

/* the 3 structures below are 100% binary compatible */ typedef struct _a { int a; double b; char c; } typedef struct _b { int d; double e; char f; } typedef struct SOME_STRUCT { int my_i; double my_f; char my_c[1]; } struct _a a = { 1, 2.5, ''z'' }; struct _b b; /* the following is valid, copy b -> a */ *(SOME_STRUCT*)&a = *(SOME_STRUCT*)b; assert((SOME_STRUCT*)&a)->my_c[0] == b.f); assert(a.c == b.f); /* more generally these identities are always true. */ assert(sizeof(a) == sizeof(b)); assert(memcmp(&a, &b, sizeof(a)) == 0); assert(pure_function_requiring_a(&a) == pure_function_requiring_a((_a*)&b)); assert(pure_function_requiring_b((b*)&a) == pure_function_requiring_b(&b)); function_requiring_a_SOME_STRUCT_pointer(&a); /* may generate a warning, but not all compiler will */ /* etc... the name space abuse is limited to the programmer''s imagination */