Alineación de miembros de estructura, relleno y empaque de datos

¿Qué entendemos por alineación de datos, empaquetamiento de estructuras y relleno?
Prediga la salida del siguiente programa.

c

#include <stdio.h>
  
// Alignment requirements
// (typical 32 bit machine)
  
// char         1 byte
// short int    2 bytes
// int          4 bytes
// double       8 bytes
  
// structure A
typedef struct structa_tag
{
   char        c;
   short int   s;
} structa_t;
  
// structure B
typedef struct structb_tag
{
   short int   s;
   char        c;
   int         i;
} structb_t;
  
// structure C
typedef struct structc_tag
{
   char        c;
   double      d;
   int         s;
} structc_t;
  
// structure D
typedef struct structd_tag
{
   double      d;
   int         s;
   char        c;
} structd_t;
  
int main()
{
   printf("sizeof(structa_t) = %lu\n", sizeof(structa_t));
   printf("sizeof(structb_t) = %lu\n", sizeof(structb_t));
   printf("sizeof(structc_t) = %lu\n", sizeof(structc_t));
   printf("sizeof(structd_t) = %lu\n", sizeof(structd_t));
  
   return 0;
}

Antes de continuar, escribe tu respuesta en un papel y sigue leyendo. Si te urge ver una explicación, es posible que no entiendas ninguna laguna en tu analogía. Alineación de datos:
cada tipo de datos en C/C++ tendrá un requisito de alineación (de hecho, lo exige la arquitectura del procesador, no el idioma). Un procesador tendrá una longitud de palabra de procesamiento como la del tamaño del bus de datos. En una máquina de 32 bits, el tamaño de la palabra de procesamiento será de 4 bytes.
 

Históricamente, la memoria es direccionable por bytes y está organizada secuencialmente. Si la memoria está organizada como un solo banco de un byte de ancho, el procesador necesita emitir 4 ciclos de lectura de memoria para obtener un número entero. Es más económico leer los 4 bytes de un entero en un ciclo de memoria. Para aprovechar esta ventaja, la memoria se organizará como un grupo de 4 bancos, como se muestra en la figura anterior.
El direccionamiento de la memoria sigue siendo secuencial. Si el banco 0 ocupa una dirección X, el banco 1, el banco 2 y el banco 3 estarán en las direcciones (X + 1), (X + 2) y (X + 3). Si se asigna un número entero de 4 bytes en la dirección X (X es múltiplo de 4), el procesador necesita solo un ciclo de memoria para leer el número entero.
Mientras que, si el número entero se asigna a una dirección que no sea un múltiplo de 4, se extiende a lo largo de dos filas de bancos, como se muestra en la siguiente figura. Tal número entero requiere dos ciclos de lectura de memoria para recuperar los datos.
 

La alineación de datos de una variable se  ocupa de la forma en que los datos se almacenan en estos bancos. Por ejemplo, la alineación natural de int en una máquina de 32 bits es de 4 bytes. Cuando un tipo de datos se alinea de forma natural, la CPU lo obtiene en ciclos de lectura mínimos.
De manera similar, la alineación natural de short int es de 2 bytes. Esto significa que un int corto se puede almacenar en el banco 0 – par banco 1 o banco 2 – par banco 3. Un doble requiere 8 bytes y ocupa dos filas en los bancos de memoria. Cualquier desalineación de double obligará a más de dos ciclos de lectura a recuperar datos double .
Tenga en cuenta que un  dobleLa variable se asignará en un límite de 8 bytes en una máquina de 32 bits y requiere dos ciclos de lectura de memoria. En una máquina de 64 bits, según el número de bancos, la variable doble se asignará en un límite de 8 bytes y requiere solo un ciclo de lectura de memoria.
Relleno de estructura:
en C/C++ se utilizan estructuras como paquete de datos. No proporciona ninguna función de encapsulación de datos ni de ocultación de datos (el caso de C++ es una excepción debido a su similitud semántica con las clases).
Debido a los requisitos de alineación de varios tipos de datos, cada miembro de la estructura debe alinearse de forma natural. Los miembros de la estructura asignados secuencialmente en orden creciente. Analicemos cada estructura declarada en el programa anterior.
Salida del programa anterior:
Por comodidad, suponga que cada variable de tipo de estructura se asigna en un límite de 4 bytes (por ejemplo, 0x0000), es decir, la dirección base de la estructura es un múltiplo de 4 (no siempre es necesario, consulte la explicación de structc_t).
estructura A
El primer elemento de structa_t es char , que está alineado en un byte, seguido de un int corto . short int está alineado en 2 bytes. Si el elemento int corto se asigna inmediatamente después del elemento char, comenzará en un límite de dirección impar. El compilador insertará un byte de relleno después del carácter para garantizar que el int corto tenga una dirección múltiplo de 2 (es decir, 2 bytes alineados). El tamaño total de structa_t será sizeof(char) + 1 (relleno) + sizeof(short), 1 + 1 + 2 = 4 bytes.
estructura B
El primer miembro de structb_t es un int corto seguido de char. Dado que char puede estar en cualquier límite de byte, no se requiere relleno entre short int y char, en total ocupan 3 bytes. El siguiente miembro es int. Si el int se asigna inmediatamente, comenzará en un límite de bytes impares. Necesitamos un relleno de 1 byte después del miembro char para que la dirección del siguiente miembro int esté alineada en 4 bytes. En total, structb_t requiere 2 + 1 + 1 (relleno) + 4 = 8 bytes.
estructura C: cada estructura también tendrá requisitos de alineación
Aplicando el mismo análisis, structc_tnecesita tamaño de (char) + relleno de 7 bytes + tamaño de (doble) + tamaño de (int) = 1 + 7 + 8 + 4 = 20 bytes. Sin embargo, el tamaño de (structc_t) será de 24 bytes. Esto se debe a que, junto con los miembros de la estructura, las variables de tipo de estructura también tendrán una alineación natural. Entendámoslo con un ejemplo. Digamos que declaramos una array de structc_t como se muestra a continuación 
 

structc_t structc_array[3];

Suponga que la dirección base de structc_array es 0x0000 para cálculos fáciles. Si structc_t ocupa 20 (0x14) bytes como calculamos, el segundo elemento de la array structc_t (indexado en 1) estará en 0x0000 + 0x0014 = 0x0014. Es la dirección de inicio del elemento de índice 1 de la array. El miembro doble de esta estructura structc_t se asignará a 0x0014 + 0x1 + 0x7 = 0x001C (28 decimal), que no es múltiplo de 8 y está en conflicto con los requisitos de alineación de doble. Como mencionamos en la parte superior, el requisito de alineación de doble es de 8 bytes.
Para evitar tal desalineación, el compilador introducirá requisitos de alineación para cada estructura. Será como la del miembro más grande de la estructura. En nuestro caso, la alineación de structa_t es 2, structb_t es 4 y structc_t es 8. Si necesitamos estructuras anidadas, el tamaño de la estructura interna más grande será la alineación de la estructura más grande inmediata.
En structc_t del programa anterior, habrá un relleno de 4 bytes después del miembro int para hacer que el tamaño de la estructura sea múltiplo de su alineación. Por lo tanto, el tamaño de (structc_t) es de 24 bytes. Garantiza la alineación correcta incluso en arreglos. Puede cruzar la verificación.
estructura D – ¿Cómo reducir el relleno?
A estas alturas, puede estar claro que el relleno es inevitable. Hay una manera de minimizar el relleno. El programador debe declarar los miembros de la estructura en orden creciente/decreciente de tamaño. Un ejemplo es structd_t dado en nuestro código, cuyo tamaño es de 16 bytes en lugar de 24 bytes de structc_t.

c

void argument_alignment_check( char c1, char c2 )
{
   // Considering downward stack
   // (on upward stack the output will be negative)
   printf("Displacement %d\n", (int)&c2 - (int)&c1);
}

Publicación traducida automáticamente

Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *