Pregunta

Durante una revisión de código, encontré un código que define una estructura simple de la siguiente manera:

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

En otros lugares, se define una matriz de estos objetos:

foo listOfFoos[SOME_NUM];

Posteriormente, las estructuras se copian sin formato en un búfer:

memcpy(pBuff,listOfFoos,3*SOME_NUM);

Este código se basa en los supuestos de que:a.) El tamaño de foo es 3 y no se aplica ningún relleno, y b.) Una matriz de estos objetos se empaqueta sin relleno entre ellos.

Lo probé con GNU en dos plataformas (RedHat 64b, Solaris 9) y funcionó en ambas.

¿Son válidas las suposiciones anteriores?En caso negativo, ¿en qué condiciones (p. ej.cambio en el sistema operativo/compilador) ¿podrían fallar?

¿Fue útil?

Solución

Se requiere que una matriz de objetos 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 está trabajando con caracteres, las suposiciones probablemente sean correctas la mayoría de las veces, pero el estándar C++ ciertamente no lo garantiza.Un compilador diferente, o incluso simplemente un cambio en los indicadores pasados ​​a su compilador actual, podría provocar que se inserte relleno entre los elementos de la estructura o después del último elemento de la estructura, o ambos.

Otros consejos

Sin duda sería más seguro que hacer:

sizeof(foo) * SOME_NUM

Si se 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 usted está haciendo suposiciones sobre el relleno y alineación en absoluto.

La mayoría de los compiladores se alinean una estructura o clase a la alineación requerida del tipo incluido más amplio. En el caso de caracteres que significa que no hay alineación y relleno, pero si se agrega un corto, por ejemplo, la clase sería de 6 bytes grande con un byte de relleno añadido entre el último y el carbón corto.

Creo que la razón por la que esto funciona porque todos los campos de la estructura son carbón de leña que se 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 depende de la orden y alineación de campo).

Veamos algún ejemplo:

#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 tiene bar y F_B alineación 2, de modo que su campo i debe estar correctamente alineado. También se puede ver que el tamaño de la barra 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 usted es un código duro en lugar de sizeof(...), obtendrá un problema aquí.

Espero que esto ayude.

Todo se reduce a la alineación de memoria. Típicas máquinas de 32 bits de lectura o escritura 4 bytes de memoria por cada intento. Esta estructura está a salvo de los problemas, ya que está regulado por dicha 4 bytes fácilmente sin problemas confuso 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

memcpy(pBuff,listOfFoos,11*SOME_NUM);

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

Por desgracia, debido al relleno de la estructura de toma de hecho hasta 12 bytes. Esto se debe a que no puede caber tres carbón y de un int en esa palabra de 4 bytes, y por lo que hay un byte de espacio acolchado hay que empuja el int en su propia palabra. Esto se hace más y más de un problema de los más diversos tipos de datos se vuelven.

Para las situaciones en que se utiliza este tipo de cosas, y no puedo evitarlo, trato de hacer la pausa de compilación cuando las presunciones ya no tienen. 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)}

Me he estado a salvo y se sustituye el número mágico 3 con un sizeof(foo) Calculo.

Mi conjetura es optimizado que codifican para las arquitecturas de procesador futuras probablemente introducir algún tipo de relleno.

Y tratando de localizar a ese tipo de error es un dolor real!

Como han dicho otros, usando sizeof (foo) es una apuesta más segura. Algunos compiladores (especialmente los esotéricos del mundo incrustado) añadirá una cabecera de 4 bytes a clases. Otros pueden hacer trucos de memoria de alineación de cobardes, dependiendo de la configuración del compilador.

Para una plataforma convencional, es probable que bien, pero no es una garantía.

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 compilar con el relleno y en el otro sin, en cuyo caso sizeof () podría dar resultados diferentes. Si los datos de matriz se pasa de un ordenador a otro que sea malinterpretada debido a que los elementos de la matriz no se encuentran donde se esperaba. Una solución consiste en asegurarse de que #pragma pack (1) se utiliza siempre que sea posible, pero eso puede no ser suficiente para las matrices. Lo mejor es prever el problema y utilizar el relleno a un múltiplo de 8 bytes por elemento de la matriz.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top