Alineación de memoria en C-structs

Estoy trabajando en la máquina de 32 bits, así que supongo que la alineación de la memoria debe ser de 4 bytes. Digamos que tengo struct

typedef struct { unsigned short v1; unsigned short v2; unsigned short v3; } myStruct; 

el tamaño real es de 6 bytes, y supongo que el tamaño alineado debe ser 8, pero sizeof(myStruct) me devuelve 6.

Sin embargo, si escribo:

 typedef struct { unsigned short v1; unsigned short v2; unsigned short v3; int i; } myStruct; 

el tamaño real es de 10 bytes, alineado es 12, y esta vez sizeof(myStruct) == 12 .

¿Alguien puede explicar cuál es la diferencia?

Al menos en la mayoría de las máquinas, un tipo solo se alinea con un límite tan grande como el propio tipo [Editar: realmente no se puede exigir ninguna alineación “más” que eso, porque hay que poder crear matrices, y no puede insertar relleno en una matriz]. En su implementación, short es aparentemente de 2 bytes e int 4 bytes.

Eso significa que su primera estructura está alineada con un límite de 2 bytes. Como todos los miembros tienen 2 bytes cada uno, no se inserta relleno entre ellos.

El segundo contiene un elemento de 4 bytes, que se alinea con un límite de 4 bytes. Dado que está precedido por 6 bytes, se insertan 2 bytes de relleno entre v3 e i , dando 6 bytes de datos en los short , dos bytes de relleno y 4 bytes más de datos en el int para un total de 12.

Olvídate de tener diferentes miembros, incluso si escribes dos estructuras cuyos miembros son exactamente iguales, con una diferencia es que el orden en que se declaran es diferente, entonces el tamaño de cada estructura puede ser (y a menudo es) diferente.

Por ejemplo, mira esto,

 #include  using namespace std; struct A { char c; char d; int i; }; struct B { char c; int i; //note the order is different! char d; }; int main() { cout << sizeof(A) << endl; cout << sizeof(B) << endl; } 

Comstack con gcc-4.3.4 , y obtienes esta salida:

 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 el estándar no habla de cómo se debe realizar el relleno, por lo que los comstackdores son libres de tomar cualquier decisión y no se puede suponer que todos los comstackdores toman la misma decisión.

Por defecto, los valores se alinean de acuerdo a su tamaño. Entonces un valor de 2 bytes como un short se alinea en un límite de 2 bytes, y un valor de 4 bytes como un int se alinea en un límite de 4 bytes

En su ejemplo, se agregan 2 bytes de relleno antes de i para garantizar que caiga en un límite de 4 bytes.

(Toda la estructura está alineada en un límite al menos tan grande como el valor más grande de la estructura, por lo que su estructura se alineará con un límite de 4 bytes).

Las reglas reales varían de acuerdo con la plataforma: la página de Wikipedia sobre la alineación de la estructura de datos tiene más detalles.

Los comstackdores generalmente le permiten controlar el empaque mediante (por ejemplo) directivas #pragma pack .

En primer lugar, aunque los detalles del relleno se dejan al comstackdor, el sistema operativo también impone algunas reglas en cuanto a los requisitos de alineación. Esta respuesta asume que estás usando gcc, aunque el sistema operativo puede variar

Para determinar el espacio ocupado por una estructura dada y sus elementos, puede seguir estas reglas:

En primer lugar, suponga que la estructura siempre comienza en una dirección que está alineada correctamente para todos los tipos de datos.

Luego, para cada entrada en la estructura:

  • El espacio mínimo necesario es el tamaño sin formato del elemento dado por sizeof(element) .
  • El requisito de alineación del elemento es el requisito de alineación del tipo de base del elemento. En particular, esto significa que el requisito de alineación para una matriz de caracteres char[20] es el mismo que el requisito para una char simple.

Finalmente, el requisito de alineación de la estructura como un todo es el máximo de los requisitos de alineación de cada uno de sus elementos.

gcc insertará el relleno después de un elemento dado para asegurarse de que el siguiente (o la estructura si estamos hablando del último elemento) esté alineado correctamente. Nunca reorganizará el orden de los elementos en la estructura, incluso si eso ahorrará memoria.

Ahora los requisitos de alineación también son un poco extraños.

  • Linux de 32 bits requiere que los tipos de datos de 2 bytes tengan una alineación de 2 bytes (sus direcciones deben ser uniformes). Todos los tipos de datos más grandes deben tener una alineación de 4 bytes (direcciones que terminan en 0x0 , 0x4 , 0x8 o 0xC ). Tenga en cuenta que esto también se aplica a los tipos de más de 4 bytes (como el double y el long double ).
  • Windows de 32 bits es más estricto porque si un tipo tiene un tamaño de K bytes, debe estar alineado por byte K. Esto significa que un double solo puede colocarse en una dirección que termina en 0x0 o 0x8 . La única excepción a esto es el long double que todavía está alineado con 4 bytes, aunque en realidad tiene 12 bytes de longitud.
  • Tanto para Linux como para Windows, en máquinas de 64 bits, un tipo de byte K debe estar alineado por byte K. De nuevo, el long double es una excepción y debe estar alineado 16 bytes.

Asumiendo:

 sizeof(unsigned short) == 2 sizeof(int) == 4 

Entonces yo personalmente usaría lo siguiente (su comstackdor puede diferir):

 unsigned shorts are aligned to 2 byte boundaries int will be aligned to 4 byte boundaries. typedef struct { unsigned short v1; // 0 bytes offset unsigned short v2; // 2 bytes offset unsigned short v3; // 4 bytes offset } myStruct; // End 6 bytes. // No part is required to align tighter than 2 bytes. // So whole structure can be 2 byte aligned. typedef struct { unsigned short v1; // 0 bytes offset unsigned short v2; // 2 bytes offset unsigned short v3; // 4 bytes offset /// Padding // 6-7 padding (so i is 4 byte aligned int i; // 8 bytes offset } myStruct; // End 12 bytes // Whole structure needs to be 4 byte aligned. // So that i is correctly aligned. 

Cada tipo de datos debe alinearse en un límite de memoria de su propio tamaño. Por lo tanto, un short debe estar alineado en un límite de 2 bytes, y un int debe estar en un límite de 4 bytes. Del mismo modo, un long long tendría que estar en un límite de 8 bytes.

En su primera estructura, dado que cada elemento es de tamaño reducido, toda la estructura puede alinearse en límites short , por lo que no necesita agregar ningún relleno al final.

En la segunda estructura, el int (presumiblemente 32 bits) necesita alinearse con la palabra, por lo que inserta el relleno entre v3 e i para alinear i .

El motivo de que el segundo sizeof(myStruct) sea 12 es el relleno que se inserta entre v3 e i para alinear i en un límite de 32 bits. Hay dos bytes de esto.

Wikipedia explica el relleno y la alineación de manera razonablemente clara.

El estándar no dice mucho sobre el diseño de las estructuras con tipos completos, depende del comstackdor. Decidió que necesita el int para comenzar en un límite para acceder a él, pero como tiene que hacer un direccionamiento de memoria sub-límite para los cortos, no hay necesidad de rellenarlos.

Suena como si estuviera alineado con bounderies según el tamaño de cada var, de modo que la dirección sea un múltiplo del tamaño al que se accede (por lo que los cortos están alineados a 2, alineados a 4, etc.), si movió uno de los cortos después el int, sizeof(mystruct) debe ser 10. Por supuesto, todo esto depende del comstackdor que se esté usando y de la configuración que use a su vez.