La determinación de la alineación de las estructuras C / C ++ en relación con sus miembros

¿Se puede encontrar la alineación de un tipo de estructura si se conocen las alineaciones de los miembros de la estructura?

P.ej. para:

struct S { a_t a; b_t b; c_t c[]; }; 

es la alineación de S = max (alineación_de (a), alineación_de (b), alineación_de (c))?

Al buscar en Internet encontré que “para los tipos estructurados, el mayor requisito de alineación de cualquiera de sus elementos determina la alineación de la estructura” (en Lo que todo progtwigdor debe saber sobre la memoria ) pero no pude encontrar nada remotamente similar en el estándar (última borrador más exactamente).


Editado: Muchas gracias por todas las respuestas, especialmente a Robert Gamble, que brindó una muy buena respuesta a la pregunta original y a los demás que contribuyeron.

En breve:

Para garantizar los requisitos de alineación para los miembros de la estructura, la alineación de una estructura debe ser al menos tan estricta como la alineación de su miembro más estricto.

En cuanto a determinar la alineación de la estructura, se presentaron algunas opciones y con un poco de investigación, esto es lo que encontré:

  • c ++ std :: tr1 :: alignment_of
    • aún no estándar, pero cercano (informe técnico 1), debe estar en C ++ 0x
    • las siguientes restricciones están presentes en el último borrador: Precondición: T debe ser un tipo completo, un tipo de referencia o un conjunto de límites desconocidos, pero no debe ser un tipo de función o un vacío (posiblemente cv calificado).
      • esto significa que mi caso de uso presentado con la matriz flexible C99 no funcionará (esto no es sorprendente ya que las matrices flexibles no son estándar c ++)
    • en el último borrador de c ++ se define en los términos de una nueva palabra clave – alignas (esto tiene el mismo requisito de tipo completo)
    • en mi opinión, si el estándar c ++ admite alguna vez las matrices flexibles C99, el requisito podría relajarse (la alineación de la estructura con la matriz flexible no debería cambiar en función del número de elementos de la matriz)
  • c ++ boost :: alignment_of
    • principalmente un reemplazo tr1
    • parece estar especializado para vacío y devuelve 0 en ese caso (esto está prohibido en el borrador c ++)
    • Nota de los desarrolladores: estrictamente hablando, solo debe confiar en el valor de ALIGNOF (T) como un múltiplo de la alineación real de T, aunque en la práctica calcula el valor correcto en todos los casos que conocemos.
    • No sé si esto funciona con arreglos flexibles, debería (podría no funcionar en general, esto se convierte en comstackdor intrínseco en mi plataforma, así que no sé cómo se comportará en el caso general)
  • Andrew Top presentó una solución de plantilla simple para calcular la alineación en las respuestas
    • Esto parece estar muy cerca de lo que está haciendo el impulso (boost también devolverá el tamaño del objeto como la alineación si es más pequeño que la alineación calculada hasta donde yo pueda ver) así que probablemente se aplique el mismo aviso
    • esto funciona con arreglos flexibles
  • use Windbg.exe para descubrir la alineación de un símbolo
    • no comstackr el tiempo, comstackdor específico, no lo prueba
  • utilizando offsetof en la estructura anónima que contiene el tipo
    • ver las respuestas, no confiable, no portátil con c ++ no POD
  • comstackdores intrínsecos, ej. MSVC __alignof
    • funciona con arreglos flexibles
    • la palabra clave alignof está en el último borrador de c ++

Si queremos usar la solución “estándar”, estamos limitados a std :: tr1 :: alignment_of, pero eso no funcionará si mezclas tu código de C ++ con las matrices flexibles de c99.

Como yo lo veo, solo hay una solución: use el antiguo struct struct:

 struct S { a_t a; b_t b; c_t c[1]; // "has" more than 1 member, strictly speaking this is undefined behavior in both c and c++ when used this way }; 

Los divergentes estándares de c y c ++ y sus crecientes diferencias son desafortunados en este caso (y en cualquier otro caso).


Otra pregunta interesante es (si no podemos encontrar la alineación de una estructura de forma portátil) cuál es el requisito de alineación más estricto posible. Hay un par de soluciones que pude encontrar:

  • boost (internamente) usa una unión de variedad de tipos y usa boost :: alignment_of en ella
  • el último borrador de c ++ contiene std :: aligned_storage
    • El valor de la alineación por defecto será el requisito de alineación más estricto para cualquier tipo de objeto C ++ cuyo tamaño no sea mayor que Len.
      • por lo que el valor de std::alignment_of< std::aligned_storage>::value debería darnos la máxima alineación
      • solo borrador, aún no estándar (si alguna vez), tr1::aligned_storage no tiene esta propiedad

Cualquier idea sobre esto también sería apreciada.

He desactivado temporalmente la respuesta aceptada para obtener más visibilidad e información sobre las nuevas subpreguntas

Aquí hay dos conceptos estrechamente relacionados:

  1. La alineación requerida por el procesador para acceder a un objeto particular
  2. La alineación que el comstackdor realmente utiliza para colocar objetos en la memoria

Para garantizar los requisitos de alineación para los miembros de la estructura, la alineación de una estructura debe ser al menos tan estricta como la alineación de su miembro más estricto. No creo que esto se describa explícitamente en el estándar, pero se puede inferir de los siguientes hechos (que se detallan individualmente en el estándar):

  • Se permite que las estructuras tengan relleno entre sus miembros (y al final)
  • Las matrices no pueden tener relleno entre sus elementos
  • Puede crear una matriz de cualquier tipo de estructura

Si la alineación de una estructura no fuera al menos tan estricta como cada uno de sus miembros, no sería posible crear una matriz de estructuras, ya que algunos miembros de la estructura algunos elementos no se alinearían correctamente.

Ahora el comstackdor debe garantizar una alineación mínima para la estructura en función de los requisitos de alineación de sus miembros, pero también puede alinear los objetos de una manera más estricta que la requerida, esto a menudo se realiza por motivos de rendimiento. Por ejemplo, muchos procesadores modernos permitirán el acceso a enteros de 32 bits en cualquier alineación, pero los accesos pueden ser significativamente más lentos si no están alineados en un límite de 4 bytes.

No existe una forma portátil de determinar la alineación impuesta por el procesador para ningún tipo dado porque el lenguaje no lo expone, aunque dado que el comstackdor conoce obviamente los requisitos de alineación del procesador de destino, podría exponer esta información como una extensión.

Tampoco hay una forma portátil (al menos en C) para determinar cómo un comstackdor realmente alineará un objeto, aunque muchos comstackdores tienen opciones para proporcionar cierto nivel de control sobre la alineación.

Escribí este tipo de código de rasgo para determinar la alineación de cualquier tipo (en base a las reglas del comstackdor ya discutidas). Lo podrías encontrar útil:

 template  class Traits { public: struct AlignmentFinder { char a; T b; }; enum {AlignmentOf = sizeof(AlignmentFinder) - sizeof(T)}; }; 

Entonces ahora puedes ir:

 std::cout << "The alignment of structure S is: " << Traits::AlignmentOf << std::endl; 

La siguiente macro devolverá el requisito de alineación de cualquier tipo (incluso si es una estructura):

 #define TYPE_ALIGNMENT( t ) offsetof( struct { char x; t test; }, test ) 

Nota: Probablemente tomé prestada esta idea de un encabezado de Microsoft en algún momento de mi pasado …


Editar: como señala Robert Gamble en los comentarios, no se garantiza que esta macro funcione. De hecho, ciertamente no funcionará muy bien si el comstackdor está configurado para empacar elementos en estructuras. Entonces, si decides usarlo, úsalo con precaución.

Algunos comstackdores tienen una extensión que le permite obtener la alineación de un tipo (por ejemplo, comenzando con VS2002, MSVC tiene una __alignof() intrínseca). Esos deben usarse cuando estén disponibles.

Como los otros mencionaron, su implementación depende. Visual Studio 2005 utiliza 8 bytes como la alineación de estructura predeterminada. Internamente, los elementos están alineados por su tamaño: un flotador tiene una alineación de 4 bytes, un doble usa 8, etc.

Puede anular el comportamiento con #pragma pack. GCC (y la mayoría de los comstackdores) tienen opciones de comstackción o pragmas similares.

Es posible asumir una alineación de estructura si conoce más detalles sobre las opciones de comstackción que están en uso. Por ejemplo, #pragma pack (1) forzará la alineación en el nivel de bytes para algunos comstackdores.

Nota al margen: sé que la pregunta era acerca de la alineación, pero un problema lateral es el relleno. Para progtwigción integrada, datos binarios, etc. – En general, no asums nada sobre la alineación de la estructura si es posible. En lugar de eso, use relleno explícito si es necesario en las estructuras. He tenido casos en los que era imposible duplicar la alineación exacta utilizada en un comstackdor con un comstackdor en una plataforma diferente sin agregar elementos de relleno. Tenía que ver con la alineación de las estructuras dentro de las estructuras, por lo que agregar elementos de relleno lo solucionó.

Si desea descubrir esto para un caso particular en Windows, abra windbg:

 Windbg.exe -z \path\to\somemodule.dll -y \path\to\symbols 

Entonces corre:

 dt somemodule!CSomeType 

No creo que el diseño de memoria esté garantizado de ninguna manera en ningún estándar de C. Esto depende mucho del vendedor y del arquitecto. Puede haber formas de hacerlo que funcionen en el 90% de los casos, pero no son estándar.

Sin embargo, estaría muy contento de que se demuestre que estoy equivocado =)

Estoy de acuerdo principalmente con Paul Betts, Ryan y Dan. En realidad, depende del desarrollador, puede mantener la alineación simbólica predeterminada de la que Robert tomó nota (la explicación de Robert es solo el comportamiento predeterminado y no de ninguna manera forzada o requerida), o puede configurar la alineación que desee / Zp [# #].

Lo que esto significa es que si tienes un typedef con flotadores, dobles largos, uchar, etc., se incluyen varios surtidos de matrices. Luego, tenga otro tipo que tenga algunos de estos miembros con formas extrañas, y un solo byte, luego otro miembro impar, simplemente se alineará según la preferencia que defina el archivo make / solution.

Como se señaló anteriormente, con el comando dt de windbg en tiempo de ejecución puede averiguar cómo el comstackdor distribuyó la estructura en la memoria.

También puede usar cualquier herramienta de lectura de pdb como dia2dump para extraer esta información de pdb estáticamente.

Modificado del blog de Peeter Joot

La alineación de la estructura C se basa en el tipo nativo de mayor tamaño en la estructura, al menos en general (una excepción es algo así como usar un entero de 64 bits en win32 donde solo se requiere alineación de 32 bits).

Si solo tiene caracteres y matrices de caracteres, una vez que agrega un int, ese int terminará comenzando en un límite de 4 bytes (con posible relleno oculto antes del miembro int). Además, si la estructura no es un múltiplo de sizeof (int), se agregará un relleno oculto al final. Lo mismo para tipos cortos y de 64 bits.

Ejemplo:

 struct blah1 { char x ; char y[2] ; }; 

sizeof (blah1) == 3

 struct blah1plusShort { char x ; char y[2] ; // <<< hidden one byte inserted by the compiler here // <<< z will start on a 2 byte boundary (if beginning of struct is aligned). short z ; char w ; // <<< hidden one byte tail pad inserted by the compiler. // <<< the total struct size is a multiple of the biggest element. // <<< This ensures alignment if used in an array. }; 

sizeof (blah1plusShort) == 8

Leí esta respuesta después de 8 años y siento que la respuesta aceptada de @Robert es correcta, pero matemáticamente incorrecta.

Para garantizar los requisitos de alineación para los miembros de la estructura, la alineación de una estructura debe ser al menos tan estricta como el mínimo común múltiplo de la alineación de sus miembros . Considere un ejemplo extraño, donde los requisitos de alineación de los miembros son 4 y 10; en cuyo caso la alineación de la estructura es LCM (4, 10) que es 20 y no 10. Por supuesto, es extraño ver plataformas con tal requisito de alineación que no es una potencia de 2, y por lo tanto para todos los casos prácticos , la alineación de la estructura es igual a la alineación máxima de sus miembros.

La razón de esto es que, solo si la dirección de la estructura comienza con el LCM de sus alineamientos de miembros, se puede satisfacer la alineación de todos los miembros y el relleno entre los miembros y el final de la estructura es independiente de la dirección de inicio .

Actualización: como se señala por @chqrlie en el comentario, C estándar no permite los valores impares de la alineación. Sin embargo, esta respuesta todavía prueba por qué la alineación de la estructura es el máximo de sus alineaciones de miembros, simplemente porque el máximo pasa a ser el mínimo común múltiplo, y por lo tanto los miembros siempre están alineados con respecto a la dirección múltiple común.