2012-04-21 17 views
6

Visual C++ ofrece un modificador de compilador (/Zp) y el pragma pack para modificar la alineación de los miembros de struct. Sin embargo, parece que tengo una idea equivocada sobre cómo funcionan.Alineación de estructura en Visual C++

Según MSDN, para un valor de alineación dado n,

La alineación de un miembro habrá en un límite que es ya sea un múltiplo de n o un múltiplo del tamaño del miembro, lo es más pequeño.

Supongamos un valor de paquete de 8 bytes (que es el valor predeterminado). Dentro de una estructura, creo que cualquier miembro cuyo tamaño sea inferior a 8 bytes tendrá un desplazamiento que es un múltiplo de su propio tamaño. Cualquier miembro cuyo tamaño sea de 8 bytes o más estará en un desplazamiento que es un múltiplo de 8 bytes.

Ahora toma el siguiente programa:

#include <tchar.h> 

#pragma pack(8) 

struct Foo { 
    int i1; 
    int i2; 
    char c; 
}; 

struct Bar { 
    char c; 
    Foo foo; 
}; 

int _tmain(int argc, _TCHAR* argv[]) { 
    int fooSize = sizeof(Foo); // yields 12 
    Bar bar; 
    int fooOffset = ((int) &bar.foo) - ((int) &bar); // yields 4 

    return 0; 
} 

La estructura Foo es de 12 bytes de tamaño. Así que dentro de Bar, esperaría que el miembro Foo esté en offset 8 (un múltiplo de 8) mientras que en realidad está en offset 4. ¿Por qué es eso?

Además, Foo realmente solo tiene 4 + 4 + 1 = 9 bytes de datos. El compilador agrega automáticamente bytes de relleno al final. Pero, de nuevo, dado un valor de alineación de 8 bytes, ¿no debería rellenar un múltiplo de 8 en lugar de 4?

¡Cualquier aclaración apreciada!

+0

¿Estás seguro de que tu 'int' solo tiene 4 bytes? ¿En qué máquina estás ejecutando esto? –

+0

@Tony: es una aplicación de 32 bits. Si un int era 8 bytes en lugar de 4, un Foo con dos de ellos no podría ser solo de 12 bytes.:-) –

Respuesta

7

Su extracto explica esto, "lo que sea más pequeño". En una plataforma de 32 bits, un int tiene 4 bytes. 4 es más pequeño que 8. Por lo tanto, tiene una alineación de 4 bytes.

El pragma pack hace que las cosas se empaqueten, no se desempaqueten. No se rellenará a menos que tenga una razón para hacerlo.

+0

Espera un minuto. Tengo una estructura de Bar con un miembro de Foo. Así que asumí que "el tamaño del miembro" se refería al tamaño de Foo, que es 12. ¿Estás diciendo que el compilador realmente está buscando/adentro/Foo y (probablemente recursivamente) buscando el miembro más grande de/primitivo/tipo? Si es así, ¿hay alguna referencia (en línea) para este comportamiento? –

+1

El compilador conoce los requisitos de alineación de cada tipo. El requisito de alineación para un 'Foo' es 4. No es el tipo más grande, sino el requisito para la estructura externa, que normalmente será el mayor requisito de alineación de cualquier tipo contenido, pero no siempre. (No puede ser del tamaño del tipo completo. De lo contrario, un número entero seguido de una estructura de 9,000 bytes tendría casi 9,000 bytes de relleno. Eso es simplemente una tontería. La página de MSDN está hablando de tipos simples cuyo requisito de alineación es igual a su tamaño). –

+0

Gracias, eso explicaría el comportamiento. - Con respecto a su argumento de que no puede ser el tamaño de la estructura completa: pensé que esa era precisamente la razón de la regla "cualquiera que sea más pequeña". Su estructura de 9,000 bytes, que es más grande que 8 bytes, estaría alineada a un múltiplo de 8 en su lugar, desperdiciando así solo 4 bytes en lugar de 8,996. Entonces, para mí, el algoritmo de MSDN realmente tenía sentido. Lástima que parece ser solo la mitad de la verdad! –

1

Como dice su cita: para probar una alineación de 8 bytes necesita 8 o más tipos de datos de bytes. Aquí hay una muestra con algunos tipos de tamaños explícitos. Además, al colocar el elemento pequeño al final no se mostrará el relleno ya que puede soltarse desde el final de la estructura.

#include <stdio.h> 
int 
main(int argc, char *argv[]) 
{ 
    struct S { 
     __int64 a; 
     __int8 b; 
     __int64 c; 
    }; 
#pragma pack(push,1) 
    struct T { 
     __int64 a; 
     __int8 b; 
     __int64 c; 
    }; 
#pragma pack(pop) 
#pragma pack(push,8) 
    struct U { 
     __int64 a; 
     __int8 b; 
     __int64 c; 
    }; 
    struct B { 
     __int8 c; 
     struct U s; 
    }; 
#pragma pack(pop) 

    printf("S %d T %d U %d B %d\n", 
      sizeof(struct S), sizeof(struct T), 
      sizeof(struct U), sizeof(struct B)); 
    return 0; 
} 

Compilación esto con VC 2010:

C:\src>cl -nologo -W3 -Od packing.c && packing.exe 
packing.c 
S 24 T 17 U 24 B 32 
4

que tener en cuenta qué es importante la alineación en el primer lugar. Está ahí para permitir que la CPU lea la memoria rápidamente sin tener que multiplexar los bytes. La CPU no lee una estructura de un trago, solo accede a sus miembros. Entonces el hecho de que la estructura de Foo sea de 12 bytes es inmaterial. Solo importa la alineación de sus miembros. Dado que ningún miembro Foo tiene un requisito de alineación mayor que 4, el miembro Bar.foo solo necesita alinearse a 4.

Si tiene 12 bytes en lugar de 9 también podría usar una explicación. El compilador agrega 3 bytes de relleno al final para que una matriz de Foo tenga los miembros alineados correctamente para cada elemento de la matriz.

Cuestiones relacionadas