2009-11-04 38 views
14

Durante una revisión de código que he encontrado un código que define una estructura simple de la siguiente manera:C Datos ++ alineación miembros y matriz Embalaje

class foo { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} 

Por otra parte, un conjunto de estos objetos se define:

foo listOfFoos[SOME_NUM]; 

Más tarde, las estructuras se prima-copian en un búfer:

memcpy(pBuff,listOfFoos,3*SOME_NUM); 

Este código se basa en la assumpti ons that: a.) El tamaño de foo es 3, y no se aplica relleno, y b.) Una matriz de estos objetos se empaqueta sin relleno entre ellos.

Lo he probado con GNU en dos plataformas (RedHat 64b, Solaris 9), y funcionó en ambos.

¿Son válidas las suposiciones anteriores? De lo contrario, ¿en qué condiciones (por ejemplo, cambio en OS/compilador) podrían fallar?

+1

Y alguien inventó std: vector ... –

+0

@Matthieu: Gracias por recordarnos. Estoy seguro de que el OP había pasado por alto eso. – nus

Respuesta

16

Se requiere una matriz de objetos para que sea contigua, por lo que nunca hay relleno entre los objetos, aunque se puede agregar relleno al final de un objeto (produciendo casi el mismo efecto).

Dado que usted está trabajando con Char's, las suposiciones son probablemente las más comunes, pero el estándar de C++ ciertamente no lo garantiza. Un compilador diferente, o incluso solo un cambio en las banderas pasadas a su compilador actual podría resultar en que se inserte relleno entre los elementos de la estructura o siguiendo el último elemento de la estructura, o ambos.

+1

Ciertamente no me sorprendería si un compilador decidiera que le gustaban las cosas en los límites de cuatro bytes, y pusiera un byte de relleno al final. –

+0

Lamentablemente, la mayoría no. – Crashworks

20

Definitivamente sería más seguro que hacer:

sizeof(foo) * SOME_NUM 
+2

no solo es más seguro, sino que es más claro y elimina un número mágico. +1 – rmeador

+0

Sí, estoy de acuerdo con eso. Creo que estaba tratando de llegar a la organización de relleno y matriz. Gracias. –

+1

esto no tiene en cuenta el relleno entre los elementos de la matriz. – nschmidt

2

yo hubiera estado a salvo y se sustituye el número mágico 3 con un sizeof(foo) supongo.

Supongo que el código optimizado para futuras arquitecturas de procesador probablemente introducirá alguna forma de relleno.

¡Y tratar de localizar ese tipo de error es un verdadero dolor!

1

Como han dicho otros, usar sizeof (foo) es una apuesta más segura. Algunos compiladores (especialmente los esotéricos en el mundo integrado) agregarán un encabezado de 4 bytes a las clases. Otros pueden hacer trucos funky alineación de memoria, dependiendo de la configuración del compilador.

Para una plataforma convencional, probablemente estés bien, pero no es una garantía.

5

Si copia la matriz como ésta se debe utilizar

memcpy(pBuff,listOfFoos,sizeof(listOfFoos)); 

Esto siempre va a funcionar todo el tiempo que asignan pBuff al mismo tamaño. De esta manera no está haciendo suposiciones sobre el relleno y la alineación en absoluto.

La mayoría de los compiladores alinean una estructura o clase con la alineación requerida del tipo más grande incluido. En su caso de caracteres, eso significa que no hay alineación y relleno, pero si agrega un texto corto, por ejemplo, su clase tendría 6 bytes de ancho y se agregaría un byte de relleno entre el último carácter y el texto corto.

2

Todo se reduce a la alineación de la memoria.Las máquinas típicas de 32 bits leen o escriben 4 bytes de memoria por intento. Esta estructura está a salvo de problemas porque cae dentro de esos 4 bytes fácilmente sin problemas confusos de relleno.

Ahora bien, si la estructura era como tal:

class foo { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
    unsigned int i; 
    unsigned int j; 
} 

Su lógica compañeros de trabajo probablemente conduciría a = 3 bytes

memcpy(pBuff,listOfFoos,11*SOME_NUM); 

(3 de Char, 2 Ints = 2 * 4 bytes, por lo 3 + 8)

Desafortunadamente, debido al relleno, la estructura en realidad ocupa 12 bytes. Esto se debe a que no se pueden insertar tres caracteres y un entero en esa palabra de 4 bytes, por lo que hay un byte de espacio acolchado que empuja el int en su propia palabra. Esto se vuelve cada vez más un problema cuanto más diversos sean los tipos de datos.

4

Creo que la razón de esto funciona porque todos los campos en la estructura son char que alinean uno. Si hay al menos un campo que no se alinea 1, la alineación de la estructura/clase no será 1 (la alineación dependerá del orden de campo y la alineación).

Vamos a ver algunos ejemplos:

#include <stdio.h> 
#include <stddef.h> 

typedef struct { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} Foo; 
typedef struct { 
    unsigned short i; 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} Bar; 
typedef struct { Foo F[5]; } F_B; 
typedef struct { Bar B[5]; } B_F; 


#define ALIGNMENT_OF(t) offsetof(struct { char x; t test; }, test) 

int main(void) { 
    printf("Foo:: Size: %d; Alignment: %d\n", sizeof(Foo), ALIGNMENT_OF(Foo)); 
    printf("Bar:: Size: %d; Alignment: %d\n", sizeof(Bar), ALIGNMENT_OF(Bar)); 
    printf("F_B:: Size: %d; Alignment: %d\n", sizeof(F_B), ALIGNMENT_OF(F_B)); 
    printf("B_F:: Size: %d; Alignment: %d\n", sizeof(B_F), ALIGNMENT_OF(B_F)); 
} 

Cuando se ejecuta, el resultado es:

Foo:: Size: 3; Alignment: 1 
Bar:: Size: 6; Alignment: 2 
F_B:: Size: 15; Alignment: 1 
B_F:: Size: 30; Alignment: 2 

Se puede ver que bar y F_B tiene alineación 2, de modo que su campo i debe estar correctamente alineado. También puede ver que Size of Bar es 6 y no 5. Del mismo modo, el tamaño de B_F (5 de Bar) es 30 y no 25.

Por lo tanto, si es un código difícil en lugar de sizeof(...), obtendrá un problema aquí.

Espero que esto ayude.

+0

se ve muy bien, desafortunadamente la estructura anónima dentro de la llamada offsetof no se compila en msvc 2010 – nus

2

En situaciones donde se usan cosas como esta, y no puedo evitarlo, trato de hacer que la compilación se rompa cuando las presunciones ya no se mantienen. Utilizo algo como lo siguiente (o Boost.StaticAssert si la situación lo permite):

static_assert(sizeof(foo) <= 3); 

// Macro for "static-assert" (only usefull on compile-time constant expressions) 
#define static_assert(exp)   static_assert_II(exp, __LINE__) 
// Macro used by static_assert macro (don't use directly) 
#define static_assert_II(exp, line) static_assert_III(exp, line) 
// Macro used by static_assert macro (don't use directly) 
#define static_assert_III(exp, line) enum static_assertion##line{static_assert_line_##line = 1/(exp)} 
0

Hay aún puede haber un problema con sizeof() cuando está de paso los datos entre dos ordenadores. En uno de ellos el código podría compilarse con relleno y en el otro sin, en cuyo caso sizeof() daría resultados diferentes. Si los datos de la matriz se pasan de una computadora a la otra, se malinterpretará porque los elementos de la matriz no se encontrarán donde se esperaba. Una solución es asegurarse de que #pragma pack (1) se use siempre que sea posible, pero eso puede no ser suficiente para las matrices. Lo mejor es prever el problema y usar el relleno en un múltiplo de 8 bytes por elemento de conjunto.