Relleno en estructuras en C

Esta es una pregunta de entrevista. Hasta ahora, solía pensar que esas preguntas eran puramente dependientes del comstackdor y no deberían preocuparme, pero ahora tengo curiosidad por ello.

Supongamos que le dan dos estructuras como:

struct A { int* a; char b; } 

y,

 struct B { char a; int* b; } 

Entonces, ¿cuál preferirías y por qué? Mi respuesta fue así (aunque de alguna manera estaba filmando en la oscuridad) que la primera estructura debería ser preferida ya que el comstackdor asigna espacio para una estructura en algunos múltiplos del tamaño de la palabra (que es el tamaño del puntero – 4 bytes en 32 máquinas de bits y 8 bytes en las de 64 bits). Entonces, para ambas estructuras, el comstackdor asignaría 8 bytes (suponiendo que es una máquina de 32 bits). Pero, en el primer caso, el relleno se realizaría después de todas mis variables (es decir, después de ayb). Entonces, incluso si, por casualidad, b obtiene algún valor que se desborde y destruya mis próximos bytes rellenos, pero mi a aún está a salvo.

No parecía muy contento y pidió una desventaja de la primera estructura sobre la segunda. No tenía mucho que decir. :RE

Por favor ayúdame con las respuestas.

No creo que haya una ventaja para ninguna de estas estructuras. Hay una (!) Constante en esta ecuación. El orden de los miembros de la estructura está garantizado como declarado.

Entonces, en caso de que sea el siguiente, la segunda estructura podría tener una ventaja, ya que probablemente tenga un tamaño más pequeño, pero no en su ejemplo, ya que probablemente tengan el mismo tamaño:

 struct { char a; int b; char c; } X; 

Vs.

 struct { char a; char b; int c; } Y; 

Un poco más de explicación con respecto a los comentarios a continuación:

Todo lo que sigue a continuación no es un 100%, sino la forma común de construir las estructuras en un sistema de 32 bits donde int es de 32 bits:

Struct X:

 | | | | | | | | | | | | | char pad pad pad ---------int---------- char pad pad pad = 12 bytes 

estructura Y:

 | | | | | | | | | char char pad pad ---------int---------- = 8 bytes 

Algunas máquinas acceden a los datos de manera más eficiente cuando los valores están alineados con algún límite. Algunos requieren que los datos estén alineados.

En las máquinas modernas de 32 bits como SPARC o Intel [34] 86, o cualquier chip Motorola del 68020 en adelante, cada uno de los datos debe ser “ autoalineado ”, comenzando en una dirección que es un múltiplo de su tipo de letra Por lo tanto, los tipos de 32 bits deben comenzar en un límite de 32 bits, los tipos de 16 bits en un límite de 16 bits, los tipos de 8 bits pueden comenzar en cualquier lugar , los tipos struct / array / union tienen la alineación de su miembro más restrictivo.

Entonces podrías tener

 struct B { char a; /* 3 bytes of padding ? More ? */ int* b; } 

Una regla simple que minimiza el relleno en el caso “ autoalineado ” (y no daña en la mayoría de los demás) es ordenar los miembros de su estructura disminuyendo el tamaño.

Personalmente no veo desventaja con la primera estructura en comparación con la segunda.

No puedo pensar en una desventaja de la primera estructura sobre la segunda en este caso particular, pero es posible encontrar ejemplos en los que la regla general de poner primero a los miembros más grandes es desventajosa:

 struct A { int* a; short b; A(short num) : b(2*num+1), a(new int[b]) {} // OOPS, `b` is used uninitialized, and a good compiler will warn. // The only way to get `b` initialized before `a` is to declare // it first in the class, or of course we could repeat `2*num+1`. } 

También escuché sobre un caso bastante complicado para estructuras grandes, donde la CPU tiene modos de direccionamiento rápido para acceder al puntero + desplazamiento, para pequeños valores de desplazamiento (hasta 8 bits, por ejemplo, o algún otro límite de un valor inmediato). Lo mejor es que se optimice en micro una estructura grande al colocar tantos campos de uso común como sea posible dentro del rango de las instrucciones más rápidas.

La CPU puede incluso tener un direccionamiento rápido para el puntero + desplazamiento y el puntero + 4 * desplazamiento. Supongamos que tiene 64 campos char y 64 campos int: si coloca los campos char primero, todos los campos de ambos tipos pueden abordarse usando las mejores instrucciones, mientras que si coloca los campos int primero, los campos char que no son 4 -alineado tendrá que accederse de forma diferente, tal vez al cargar una constante en un registro en lugar de con un valor inmediato, ya que están fuera del límite de 256 bytes.

Nunca tuve que hacerlo yo mismo, y por ejemplo x86 permite grandes valores inmediatos de todos modos. No es el tipo de optimización que alguien normalmente pensaría a menos que pasen mucho tiempo mirando el ensamblaje.

En resumen, no hay ninguna ventaja al elegir cualquiera en el caso general . La única situación en la que la elección importaría en la práctica es si el empaquetado de la estructura está habilitado , en el caso de que la struct A sería una mejor opción (ya que ambos campos estarían alineados en la memoria, mientras que en la struct B el campo b estaría ubicado en una posición impar compensar). El empaquetado estructural significa que no se insertan bytes de relleno dentro de la estructura.

Sin embargo, este es un escenario bastante raro: el empaquetado de estructuras generalmente solo se habilita en situaciones específicas. No es una preocupación en la mayoría de los progtwigs. Y tampoco es controlable a través de cualquier construcción portátil en el estándar C.

Esto también es una suposición, pero la mayoría de los comstackdores tienen una opción de desalineación que explícitamente no agregará bytes de relleno. Esto luego requiere (en algunas plataformas) una corrección de tiempo de ejecución (trampa de hardware) para alinear accesos sobre la marcha (con la correspondiente penalización de rendimiento). Si recuerdo bien HPUX cayó en esta categoría. Entonces, la primera estructura de los campos aún está alineada, incluso cuando se usan las opciones del comstackdor de alineación incorrecta (porque como dijiste, el relleno estaría al final).