¿Se requiere que el tamaño de una estructura sea un múltiplo exacto de la alineación de esa estructura?

Una vez más, estoy cuestionando una creencia de larga data.

Hasta hoy, creía que la alineación de la siguiente estructura normalmente sería 4 y el tamaño normalmente sería 5 …

struct example { int m_Assume_32_Bits; char m_Assume_8_Bit_Bytes; }; 

Debido a esta suposición, tengo un código de estructura de datos que utiliza offsetof para determinar la distancia en bytes entre dos elementos adyacentes en una matriz. Hoy vi un viejo código que usaba sizeof donde no debería, no podía entender por qué no había tenido errores, codificaba una prueba unitaria, y la prueba me sorprendió al pasar.

Un poco de investigación mostró que el tamaño del tipo que utilicé para la prueba (similar a la estructura anterior) era un múltiplo exacto de la alineación, es decir, 8 bytes. Tenía relleno después del miembro final. Aquí hay un ejemplo de por qué nunca esperé esto …

 struct example2 { example m_Example; char m_Why_Cant_This_Be_At_Offset_6_Bytes; }; 

Un poco de Google arrojó ejemplos que dejan en claro que este relleno después del miembro final está permitido, por ejemplo http://en.wikipedia.org/wiki/Data_structure_alignment#Data_structure_padding (el bit “o al final de la estructura”) .

Esto es un poco embarazoso, ya que recientemente publiqué este comentario: uso de relleno estructural (mi primer comentario a esa respuesta).

Lo que no puedo determinar es si este relleno a un múltiplo exacto de la alineación está garantizado por el estándar de C ++, o si es algo que está permitido y que algunos (pero quizás no todos) comstackn.

Entonces, ¿se requiere que el tamaño de una estructura sea un múltiplo exacto de la alineación de esa estructura de acuerdo con el estándar de C ++?

Si el estándar C hace garantías diferentes, me interesa también, pero el foco está en C ++.

Una definición de tamaño de alineación :

El tamaño de alineación de una estructura es el desplazamiento de un elemento al siguiente cuando tiene una matriz de esa estructura.

Por su naturaleza, si tiene una matriz de una estructura con dos elementos, ambos necesitan tener miembros alineados, por lo que eso significa que sí, el tamaño tiene que ser un múltiplo de la alineación. (No estoy seguro de si algún estándar explícitamente impone esto, pero como el tamaño y la alineación de una estructura no dependen de si la estructura está sola o dentro de una matriz, las mismas reglas se aplican a ambas, por lo que no puede realmente ser de otra manera.)

5.3.3 / 2

Cuando se aplica a una clase, el resultado [de sizeof] es el número de bytes en un objeto de esa clase, incluido cualquier relleno requerido para colocar objetos de ese tipo en una matriz.

Entonces sí, el tamaño del objeto es un múltiplo de su alineación.

El estándar dice (sección [dcl.array] :

Un objeto del tipo de matriz contiene un conjunto no vacío asignado contiguamente de subobjetos N de tipo T.

Por lo tanto, no hay relleno entre los elementos de la matriz.

La norma no exige el relleno de las estructuras internas, pero el estándar no permite ninguna otra forma de alinear los elementos de la matriz.

No estoy seguro si esto está en el estándar real de C / C ++, y me inclino a decir que depende del comstackdor (solo para estar seguro). Sin embargo, pasé un tiempo “divertido” descubriendo eso hace unos meses, cuando tuve que enviar estructuras C dinámicamente generadas como matrices de bytes a través de una red como parte de un protocolo, para comunicarme con un chip. La alineación y el tamaño de todas las estructuras tenían que ser coherentes con las estructuras en el código que se ejecuta en el chip, que se compiló con una variante de GCC para la architecture MIPS. Intentaré dar el algoritmo, y debería aplicarse a todas las variantes de gcc (y afortunadamente a la mayoría de los otros comstackdores).

Todos los tipos base, como char , short e int se alinean con su tamaño, y se alinean a la siguiente posición disponible, independientemente de la alineación del elemento principal . Y para responder la pregunta original, sí, el tamaño total es un múltiplo de la alineación.

 // size 8 struct { char A; //byte 0 char B; //byte 1 int C; //byte 4 }; 

Aunque la alineación de la estructura es de 4 bytes, los caracteres se empaquetan lo más cerca posible.

La alineación de una estructura es igual a la alineación más grande de sus miembros .

Ejemplo:

 //size 4, but alignment is 2! struct foo { char A; //byte 0 char B; //byte 1 short C; //byte 3 } //size 6 struct bar { char A; //byte 0 struct foo B; //byte 2 } 

Esto también se aplica a los sindicatos, y de una manera curiosa. El tamaño de una unión puede ser mayor que cualquiera de los tamaños de sus miembros, simplemente debido a la alineación:

 //size 3, alignment 1 struct foo { char A; //byte 0 char B; //byte 1 char C; //byte 2 }; //size 2, alignment 2 struct bar { short A; //byte 0 }; //size 4! alignment 2 union foobar { struct foo A; struct bar B; } 

Usando estas reglas simples, usted debería ser capaz de descubrir la alineación / tamaño de cualquier unión / estructura horriblemente anidada que encuentre. Esto es todo de la memoria, así que si me he perdido un caso de esquina que no se puede decidir a partir de estas reglas, ¡por favor hágamelo saber!

Entonces, divida su pregunta en dos:

1. ¿Es legal?

[5.3.3.2] Cuando se aplica a una clase, el resultado [del operador sizeof ()] es el número de bytes en un objeto de esa clase, incluido cualquier relleno requerido para colocar objetos de ese tipo en una matriz.

Entonces, no, no lo es.

2. Bueno, ¿por qué no?

Aquí, solo puedo especular.

2.1. Los aritméticos de punteros se vuelven más raros
Si la alineación sería “entre elementos de la matriz” pero no afectaría el tamaño, zthigns se complicaría innecesariamente, por ejemplo

 (char *)(X+1) != ((char *)X) + sizeof(X) 

(Tengo la corazonada de que esto es requerido implícitamente por el estándar incluso sin la statement anterior, pero no puedo ponerlo como prueba)

2.2 Simplicidad
Si la alineación afecta el tamaño, la alineación y el tamaño pueden decidirse mirando un solo tipo. Considera esto:

 struct A { int x; char y; } struct B { A left, right; } 

Con el estándar actual, solo necesito saber sizeof (A) para determinar el tamaño y el diseño de B.
Con la alternativa, sugieres que necesito conocer las partes internas de A. Similar a tu example2 : para un “mejor empaque”, el tamaño (por ejemplo) no es suficiente, debes considerar las partes internas del ejemplo.

C ++ no lo dice explícitamente, pero es una consecuencia de otros dos requisitos:

Primero, todos los objetos deben estar bien alineados.

3.8 / 1 dice

La vida útil de un objeto de tipo T comienza cuando se obtiene […] el almacenamiento con la alineación y el tamaño adecuados para el tipo T

y 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 un número de bytes; un objeto se asigna a una dirección que cumple los requisitos de alineación de su tipo de objeto.

Por lo tanto, cada objeto debe alinearse de acuerdo con sus requisitos de alineación.

El otro requisito es que los objetos en una matriz se asignan de manera contigua:

8.3.4 / 1:

Un objeto del tipo de matriz contiene un conjunto no vacío contiguamente asignado de N subobjetos de tipo T

Para que los objetos en una matriz se asignen contiguamente, no puede haber espacio entre ellos. Pero para que cada objeto de la matriz se alinee correctamente, cada objeto individual debe rellenarse para que el byte inmediatamente después del final del objeto también esté bien alineado. En otras palabras, el tamaño del objeto debe ser un múltiplo de su alineación.

El estándar dice muy poco sobre relleno y alineamiento. Muy poco está garantizado. Lo único en lo que puedes apostar es que el primer elemento está al principio de la estructura. Después de eso … la alineación y el relleno pueden ser cualquier cosa.

Parece que el estándar C ++ 03 no dijo (o no lo encontré) si los bytes de relleno de alineación deberían incluirse en la representación del objeto.

Y el estándar C99 dice que el “tamaño de” un tipo de estructura o tipo de unión incluye relleno interno y final, pero no estoy seguro de si todo el relleno de alineación está incluido en ese “relleno posterior”.

Ahora volvamos a tu ejemplo. Realmente no hay confusión. sizeof(example) == 8 significa que la estructura toma 8 bytes para representarse a sí misma, incluidos los bytes de relleno de seguimiento 3. Si el carácter de la segunda estructura tiene un desplazamiento de 6, sobrescribirá el espacio utilizado por m_Example . El diseño de un cierto tipo está definido por la implementación y debe mantenerse estable en toda la implementación.

Aún así, si p+1 es igual a (T*)((char*)p + sizeof(T)) no está seguro. Y espero encontrar la respuesta.