Relleno en estructuras en C


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

Supongamos que se 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 estaba disparando en la oscuridad) que la primera estructura debería ser preferida ya que el compilador asigna espacio para una estructura en algunos múltiplos de el tamaño de la palabra (que es el tamaño del puntero: 4 bytes en máquinas de 32 bits y 8 bytes en máquinas de 64 bits). Por lo tanto, para ambas estructuras el compilador asignaría 8 bytes(asumiendo que es una máquina de 32 bits). Pero, en el primer caso, el relleno se haría después de todas mis variables (es decir, después de a y b). Así que incluso si por alguna casualidad, b obtiene algún valor que se desborda y destruye mis próximos bytes acolchados, pero mi a sigue siendo seguro.

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

Por favor ayúdame con las respuestas.

Author: nsane, 2011-08-06

5 answers

No creo que haya una ventaja para ninguna de estas estructuras. Hay uno(! constante en esta ecuación. Se garantiza que el orden de los miembros de la estructura sea el declarado.

Así que en caso como el siguiente, la segunda estructura podría tener una ventaja, ya que probablemente tiene un tamaño más pequeño, pero no en su ejemplo, ya que probablemente tendrán 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 abajo:

Todo lo siguiente no es un 100%, sino la forma común en que las estructuras se construirán en un sistema de 32 bits donde int es de 32 bits:

Estructura X:

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

Estructura Y:

|     |     |     |     |     |     |     |     |
 char  char  pad   pad   ---------int----------        = 8 bytes
 33
Author: MByD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-07 00:49:12

Algunas máquinas acceden a los datos de manera más eficiente cuando los valores se alinean con algún límite. Algunos requerir datos a alinear.

En máquinas modernas de 32 bits como SPARC o Intel [34] 86, o cualquier El microprocesador de Motorola del 68020 para arriba, cada dato iten debe ser generalmente "auto-alineado", comenzando en una dirección que es un múltiplo de su tamaño del tipo. Por lo tanto, los tipos de 32 bits deben comenzar en un límite de 32 bits, 16 bits tipos en un límite de 16 bits, tipos de 8 bits puede comenzar en cualquier lugar, struct/array / union types have the alignment of their most restrictive miembro.

Así que usted podría tener

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

Una regla simple que minimiza el relleno en el caso "auto-alineado" (y no hace daño en la mayoría de los demás) es ordenar sus miembros de estructura por tamaño decreciente.

Personalmente no veo desventaja con la primera estructura cuando se compara con la segunda.

 11
Author: cnicutar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-06 17:54:18

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

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 he oído hablar de un caso bastante complicado para estructuras grandes, donde la CPU tiene modos de direccionamiento rápido para acceder al puntero+offset, para valores pequeños de offset (hasta 8 bits, por ejemplo, o algún otro límite de un valor inmediato). Mejor micro-optimice una estructura grande poniendo tantos de los campos más comúnmente utilizados como sea posible dentro del alcance de las instrucciones más rápidas.

La CPU podría incluso tener un direccionamiento rápido para puntero+offset y puntero+4*offset. Entonces supongamos que tiene 64 campos char y 64 campos int: si coloca los campos char primero, entonces todos los campos de ambos tipos se pueden abordar usando las mejores instrucciones, mientras que si coloca los campos int primero, entonces los campos char que no están alineados con 4 solo tendrán que ser se accede de manera diferente, tal vez cargando una constante en un registro en lugar de con un valor inmediato, porque 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 cualquiera normalmente pensaría a menos que pasen mucho tiempo mirando el montaje.

 4
Author: Steve Jessop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-06 23:45:06

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

Sin embargo, este es un escenario bastante poco común: estructura el embalaje generalmente solo se habilita en situaciones específicas. No es una preocupación en la mayoría de los programas. Y tampoco es controlable a través de ninguna construcción portátil en el estándar C.

 2
Author: alecov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-06 19:07:41

Esto también es una conjetura, pero la mayoría de los compiladores tienen una opción de desalineación que explícitamente no agregará bytes de relleno. Esto requiere (en algunas plataformas) una corrección de tiempo de ejecución (trampa de hardware) para alinear los accesos sobre la marcha (con la correspondiente penalización de rendimiento). Si no recuerdo mal, HPUX cayó en esta categoría. Por lo tanto, la primera estructura los campos siguen alineados incluso cuando se usan opciones de compilador de desalineación (porque, como dijo, el relleno estaría al final).

 1
Author: Kevin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-06 17:47:56