Estructura de relleno y embalaje


209

Considerar:

struct mystruct_A
{
   char a;
   int b;
   char c;
} x;

struct mystruct_B
{
   int b;
   char a;
} y;

Los tamaños de las estructuras son 12 y 8 respectivamente.

¿Estas estructuras están acolchadas o empaquetadas?

¿Cuándo se realiza el relleno o el embalaje?



24
The Lost Art of C Structure Packing - catb.org/esr/structure-packing
Paolo

paddinghace las cosas más grandes packinghace las cosas más pequeñas Totalmente diferente.
smwikipedia

Respuestas:


264

El relleno alinea a los miembros de la estructura con los límites de direcciones "naturales"; por ejemplo, los intmiembros tendrían compensaciones, que están mod(4) == 0en la plataforma de 32 bits. El relleno está activado de forma predeterminada. Inserta los siguientes "vacíos" en su primera estructura:

struct mystruct_A {
    char a;
    char gap_0[3]; /* inserted by compiler: for alignment of b */
    int b;
    char c;
    char gap_1[3]; /* -"-: for alignment of the whole struct in an array */
} x;

El embalaje , por otro lado, evita que el compilador realice relleno (esto debe solicitarse explícitamente) en GCC __attribute__((__packed__)), por lo que es lo siguiente:

struct __attribute__((__packed__)) mystruct_A {
    char a;
    int b;
    char c;
};

produciría estructura de tamaño 6 en una arquitectura de 32 bits.

Sin embargo, una nota: el acceso a la memoria no alineado es más lento en las arquitecturas que lo permiten (como x86 y amd64), y está explícitamente prohibido en arquitecturas de alineación estrictas como SPARC.


2
Me pregunto: ¿la prohibición de la memoria no alineada en la chispa significa que no puede manejar una matriz de bytes habitual? El empaque de estructuras, como sé, se usa principalmente para transmitir (es decir, redes) datos, cuando necesita convertir una matriz de bytes en una estructura, y asegúrese de que una matriz se ajuste a los campos de una estructura. Si la chispa no puede hacer eso, ¿cómo trabajan esos?
Hola Ángel,

14
Esa es exactamente la razón por la cual, si observa los diseños de encabezado IP, UDP y TCP, verá que todos los campos enteros están alineados.
Nikolai Fetissov

17
El "Arte perdido del embalaje de estructuras en C" explica las optimizaciones de relleno y embalaje - catb.org/esr/structure-packing
Rob11311

3
¿El primer miembro tiene que venir primero? Pensé que la organización depende totalmente de la implementación, y no se puede confiar en ella (incluso de una versión a otra).
allyourcode

44
+ allyourcode El estándar garantiza que se mantendrá el orden de los miembros y que el primer miembro comenzará con un desplazamiento de 0.
martinkunev

64

( Las respuestas anteriores explicaron el motivo con bastante claridad, pero no parece del todo claro sobre el tamaño del relleno, por lo tanto, agregaré una respuesta de acuerdo con lo que aprendí de The Lost Art of Structure Packing , ha evolucionado para no limitarse C, pero también es aplicable a Go, Rust. )


Alinear memoria (para estructura)

Reglas:

  • Antes de cada miembro individual, habrá relleno para que comience en una dirección que es divisible por su tamaño.
    por ejemplo, en un sistema de 64 bits, intdebe comenzar en la dirección divisible por 4, y longpor 8, shortpor 2.
  • chary char[]son especiales, podrían ser cualquier dirección de memoria, por lo que no necesitan relleno antes de ellos.
  • Para struct, además de la necesidad de alineación para cada miembro individual, el tamaño de la estructura completa en sí se alineará con un tamaño divisible por el tamaño del miembro individual más grande, mediante relleno al final.
    por ejemplo, si el miembro más grande de struct es longdivisible por 8, intluego por 4 y shortluego por 2.

Orden de miembro:

  • El orden de los miembros puede afectar el tamaño real de la estructura, así que téngalo en cuenta. por ejemplo, el stu_cy stu_ddel ejemplo a continuación tienen los mismos miembros, pero en diferente orden, y resultan en un tamaño diferente para las 2 estructuras.

Dirección en memoria (para estructura)

Reglas:


  • La dirección de estructura del sistema de 64 bits comienza desde (n * 16)bytes. ( Puede ver en el siguiente ejemplo, todas las direcciones hexadecimales impresas de las estructuras terminan con0 ) .
    Motivo : el miembro de estructura individual más grande posible es 16 bytes ( long double).
  • (Actualización) Si una estructura solo contiene uncharcomo miembro, su dirección podría comenzar en cualquier dirección.

Espacio vacio :

  • El espacio vacío entre 2 estructuras podría ser utilizado por variables no estructuradas que podrían encajar. Por
    ejemplo, a test_struct_address()continuación, la variable xreside entre la estructura adyacente gy h.
    No importa si xse declara, hla dirección no cambiará, xsolo reutilizará el espacio vacío que se gdesperdició.
    Caso similar para y.

Ejemplo

( para sistema de 64 bits )

memory_align.c :

/**
 * Memory align & padding - for struct.
 * compile: gcc memory_align.c
 * execute: ./a.out
 */ 
#include <stdio.h>

// size is 8, 4 + 1, then round to multiple of 4 (int's size),
struct stu_a {
    int i;
    char c;
};

// size is 16, 8 + 1, then round to multiple of 8 (long's size),
struct stu_b {
    long l;
    char c;
};

// size is 24, l need padding by 4 before it, then round to multiple of 8 (long's size),
struct stu_c {
    int i;
    long l;
    char c;
};

// size is 16, 8 + 4 + 1, then round to multiple of 8 (long's size),
struct stu_d {
    long l;
    int i;
    char c;
};

// size is 16, 8 + 4 + 1, then round to multiple of 8 (double's size),
struct stu_e {
    double d;
    int i;
    char c;
};

// size is 24, d need align to 8, then round to multiple of 8 (double's size),
struct stu_f {
    int i;
    double d;
    char c;
};

// size is 4,
struct stu_g {
    int i;
};

// size is 8,
struct stu_h {
    long l;
};

// test - padding within a single struct,
int test_struct_padding() {
    printf("%s: %ld\n", "stu_a", sizeof(struct stu_a));
    printf("%s: %ld\n", "stu_b", sizeof(struct stu_b));
    printf("%s: %ld\n", "stu_c", sizeof(struct stu_c));
    printf("%s: %ld\n", "stu_d", sizeof(struct stu_d));
    printf("%s: %ld\n", "stu_e", sizeof(struct stu_e));
    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));

    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));

    return 0;
}

// test - address of struct,
int test_struct_address() {
    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));
    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));

    struct stu_g g;
    struct stu_h h;
    struct stu_f f1;
    struct stu_f f2;
    int x = 1;
    long y = 1;

    printf("address of %s: %p\n", "g", &g);
    printf("address of %s: %p\n", "h", &h);
    printf("address of %s: %p\n", "f1", &f1);
    printf("address of %s: %p\n", "f2", &f2);
    printf("address of %s: %p\n", "x", &x);
    printf("address of %s: %p\n", "y", &y);

    // g is only 4 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "g", "h", (long)(&h) - (long)(&g));

    // h is only 8 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "h", "f1", (long)(&f1) - (long)(&h));

    // f1 is only 24 bytes itself, but distance to next struct is 32 bytes(on 64 bit system) or 24 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "f1", "f2", (long)(&f2) - (long)(&f1));

    // x is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between g & h,
    printf("space between %s and %s: %ld\n", "x", "f2", (long)(&x) - (long)(&f2));
    printf("space between %s and %s: %ld\n", "g", "x", (long)(&x) - (long)(&g));

    // y is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between h & f1,
    printf("space between %s and %s: %ld\n", "x", "y", (long)(&y) - (long)(&x));
    printf("space between %s and %s: %ld\n", "h", "y", (long)(&y) - (long)(&h));

    return 0;
}

int main(int argc, char * argv[]) {
    test_struct_padding();
    // test_struct_address();

    return 0;
}

Resultado de ejecución - test_struct_padding():

stu_a: 8
stu_b: 16
stu_c: 24
stu_d: 16
stu_e: 16
stu_f: 24
stu_g: 4
stu_h: 8

Resultado de ejecución - test_struct_address():

stu_g: 4
stu_h: 8
stu_f: 24
address of g: 0x7fffd63a95d0  // struct variable - address dividable by 16,
address of h: 0x7fffd63a95e0  // struct variable - address dividable by 16,
address of f1: 0x7fffd63a95f0 // struct variable - address dividable by 16,
address of f2: 0x7fffd63a9610 // struct variable - address dividable by 16,
address of x: 0x7fffd63a95dc  // non-struct variable - resides within the empty space between struct variable g & h.
address of y: 0x7fffd63a95e8  // non-struct variable - resides within the empty space between struct variable h & f1.
space between g and h: 16
space between h and f1: 16
space between f1 and f2: 32
space between x and f2: -52
space between g and x: 12
space between x and y: 12
space between h and y: 8

Por lo tanto, el inicio de la dirección para cada variable es g: d0 x: dc h: e0 y: e8

ingrese la descripción de la imagen aquí


44
Las "reglas" en realidad lo dejaron muy claro, no pude encontrar una regla sencilla en ningún lado. Gracias.
Pervez Alam

2
@PervezAlam El libro <The Lost Art of C Structure Packing>explica bastante bien las reglas, incluso aunque es un poco más largo que esta respuesta. El libro está disponible gratuitamente en línea: catb.org/esr/structure-packing
Eric Wang

Lo intentaré, por cierto, ¿se limita al embalaje de estructura? Solo curiosidades ya que me gustó la explicación en el libro.
Pervez Alam

1
@PervezAlam Es un libro muy corto, enfocado principalmente en la tecnología que reduciría la huella de memoria del programa c, solo toma como máximo varios días para terminar de leer.
Eric Wang

1
@ValidusOculus Sí, significa 16 bytes alineados.
Eric Wang

44

Sé que esta pregunta es antigua y la mayoría de las respuestas aquí explican muy bien el relleno, pero al tratar de comprenderlo, pensé que ayudaría tener una imagen "visual" de lo que estaba sucediendo.

El procesador lee la memoria en "fragmentos" de un tamaño definido (palabra). Digamos que la palabra del procesador tiene 8 bytes de longitud. Verá la memoria como una gran fila de bloques de construcción de 8 bytes. Cada vez que necesite obtener información de la memoria, alcanzará uno de esos bloques y la obtendrá.

Alineación de variables

Como parece en la imagen de arriba, no importa dónde esté un Char (1 byte de largo), ya que estará dentro de uno de esos bloques, lo que requiere que la CPU procese solo 1 palabra.

Cuando manejamos datos de más de un byte, como un byte int de 4 byte o un doble de 8 byte, la forma en que se alinean en la memoria hace una diferencia en cuántas palabras tendrá que procesar la CPU. Si los fragmentos de 4 bytes están alineados de manera que siempre quepan en el interior de un bloque (la dirección de memoria es un múltiplo de 4), solo se tendrá que procesar una palabra. De lo contrario, un trozo de 4 bytes podría tener parte de sí mismo en un bloque y parte en otro, requiriendo que el procesador procese 2 palabras para leer estos datos.

Lo mismo se aplica a un doble de 8 bytes, excepto que ahora debe estar en una dirección de memoria múltiplo de 8 para garantizar que siempre estará dentro de un bloque.

Esto considera un procesador de texto de 8 bytes, pero el concepto se aplica a otros tamaños de palabras.

El relleno funciona rellenando los espacios entre esos datos para asegurarse de que estén alineados con esos bloques, mejorando así el rendimiento al leer la memoria.

Sin embargo, como se indica en otras respuestas, a veces el espacio es más importante que el rendimiento en sí mismo. Tal vez esté procesando muchos datos en una computadora que no tiene mucha RAM (se podría usar el espacio de intercambio, pero es MUCHO más lento). Puede organizar las variables en el programa hasta que se complete el menor relleno (como se ejemplificó en otras respuestas) pero si eso no es suficiente, puede desactivar explícitamente el relleno, que es lo que es el empaque .


3
Esto no explica el empaquetamiento de la estructura pero ilustra muy bien la alineación de palabras de la CPU.
David Foerster


1
@ CiroSantilli709 大 抓捕 六四 事件 法轮功, estaba en gimp, pero supongo que habría ahorrado algo de tiempo haciéndolo en pintura, jaja
IanC

1
Aún mejor desde el código abierto (Y)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

21

El relleno de estructura suprime el relleno de estructura, el relleno utilizado cuando la alineación es más importante, el relleno utilizado cuando el espacio es más importante.

Algunos compiladores proporcionan #pragmasuprimir el relleno o empaquetarlo en n número de bytes. Algunos proporcionan palabras clave para hacer esto. Generalmente, el pragma que se usa para modificar el relleno de la estructura estará en el formato siguiente (depende del compilador):

#pragma pack(n)

Por ejemplo, ARM proporciona la __packedpalabra clave para suprimir el relleno de estructura. Consulte el manual del compilador para obtener más información al respecto.

Por lo tanto, una estructura compacta es una estructura sin relleno.

Generalmente se utilizarán estructuras empaquetadas

  • para ahorrar espacio

  • para formatear una estructura de datos para transmitir a través de la red utilizando algún protocolo (esta no es una buena práctica, por supuesto, porque necesita
    lidiar con la resistencia)


5

El relleno y el embalaje son solo dos aspectos de la misma cosa:

  • embalaje o alineación es el tamaño al que se redondea cada miembro
  • el relleno es el espacio extra agregado para que coincida con la alineación

En mystruct_A, suponiendo una alineación predeterminada de 4, cada miembro se alinea en un múltiplo de 4 bytes. Como el tamaño de chares 1, el relleno para ay ces 4 - 1 = 3 bytes, mientras que no se requiere relleno para el int bque ya es de 4 bytes. Funciona de la misma manera para mystruct_B.


1

El empaquetamiento de estructura solo se realiza cuando le indica a su compilador explícitamente que empaquete la estructura. El relleno es lo que estás viendo. Su sistema de 32 bits está rellenando cada campo para la alineación de palabras. Si le hubiera dicho a su compilador que empaquetara las estructuras, serían 6 y 5 bytes, respectivamente. Aunque no hagas eso. No es portátil y hace que los compiladores generen código mucho más lento (y a veces incluso con errores).


1

¡No hay peros al respecto! Quien quiera comprender el tema debe hacer lo siguiente:


1

Reglas para el relleno:

  1. Cada miembro de la estructura debe estar en una dirección divisible por su tamaño. El relleno se inserta entre los elementos o al final de la estructura para asegurarse de que se cumpla esta regla. Esto se hace para un acceso de bus más fácil y eficiente por el hardware.
  2. El relleno al final de la estructura se decide en función del tamaño del miembro más grande de la estructura.

Por qué Regla 2: Considere la siguiente estructura,

Estructura 1

Si tuviéramos que crear una matriz (de 2 estructuras) de esta estructura, no se requerirá relleno al final:

Matriz Struct1

Por lo tanto, el tamaño de struct = 8 bytes

Supongamos que creamos otra estructura de la siguiente manera:

Estructura 2

Si tuviéramos que crear una matriz de esta estructura, hay 2 posibilidades, de la cantidad de bytes de relleno necesarios al final.

A. Si agregamos 3 bytes al final y lo alineamos para int y no Long:

Matriz Struct2 alineada a int

B. Si agregamos 7 bytes al final y lo alineamos para Long:

Matriz Struct2 alineada a Long

La dirección de inicio de la segunda matriz es un múltiplo de 8 (es decir, 24). El tamaño de la estructura = 24 bytes.

Por lo tanto, al alinear la dirección de inicio de la siguiente matriz de la estructura con un múltiplo del miembro más grande (es decir, si creáramos una matriz de esta estructura, la primera dirección de la segunda matriz debe comenzar en una dirección que es un múltiplo del miembro más grande de la estructura. Aquí está, 24 (3 * 8)), podemos calcular el número de bytes de relleno necesarios al final.


-1

La alineación de la estructura de datos es la forma en que los datos se organizan y acceden en la memoria de la computadora. Consiste en dos problemas separados pero relacionados: alineación de datos y relleno de estructura de datos . Cuando una computadora moderna lee o escribe en una dirección de memoria, lo hará en fragmentos de tamaño de palabra (por ejemplo, fragmentos de 4 bytes en un sistema de 32 bits) o mayor. La alineación de datos significa colocar los datos en una dirección de memoria igual a un múltiplo del tamaño de la palabra, lo que aumenta el rendimiento del sistema debido a la forma en que la CPU maneja la memoria. Para alinear los datos, puede ser necesario insertar algunos bytes sin sentido entre el final de la última estructura de datos y el comienzo de la siguiente, que es el relleno de la estructura de datos.

  1. Para alinear los datos en la memoria, se insertan uno o más bytes (direcciones) vacíos (o se dejan vacíos) entre las direcciones de memoria que se asignan para otros miembros de la estructura durante la asignación de memoria. Este concepto se llama relleno de estructura.
  2. La arquitectura de un procesador de computadora es tal que puede leer 1 palabra (4 bytes en un procesador de 32 bits) de la memoria a la vez.
  3. Para aprovechar esta ventaja del procesador, los datos siempre se alinean como un paquete de 4 bytes que conduce a insertar direcciones vacías entre la dirección de otro miembro.
  4. Debido a este concepto de relleno de estructura en C, el tamaño de la estructura no siempre es el mismo que pensamos.

1
¿Por qué necesita vincular el mismo artículo 5 veces en su respuesta? Mantenga solo un enlace al ejemplo. Además, dado que está vinculando a su artículo, debe revelar ese hecho.
Artjom B.
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.