tipos Alineación de miembros de datos C++ y empaque de matriz




tipos de datos en c++ (8)

Durante una revisión del código, he encontrado un código que define una estructura simple de la siguiente manera:

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

En otro lugar, se define una matriz de estos objetos:

foo listOfFoos[SOME_NUM];

Más tarde, las estructuras se copian en bruto en un buffer:

memcpy(pBuff,listOfFoos,3*SOME_NUM);

Este código se basa en las suposiciones de que: a.) El tamaño de foo es 3 y no se aplica relleno, y b) Una matriz de estos objetos está empaquetada sin relleno entre ellos.

Lo probé 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?


Aún puede haber un problema con sizeof () cuando transfiere datos entre dos computadoras. 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.


Para situaciones donde se usan cosas como esta, y no puedo evitarlo, intento hacer que la compilación se rompa cuando las suposiciones ya no se cumplen. Uso 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)}

Hubiera estado a salvo y reemplacé el número mágico 3 con un sizeof(foo) creo.

Mi suposición es que el código optimizado para futuras arquitecturas de procesador probablemente introducirá alguna forma de relleno.

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


Se requiere una serie de objetos para que sean contiguos, 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 Char's, las suposiciones son probablemente las correctas más de las veces, 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.


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.


Creo que la razón por la que esto funciona es porque todos los campos de la estructura son char y 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).

Veamos un 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

Puedes ver que Bar y F_B tienen alineación 2 para que su campo i esté alineado correctamente. 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 .

Entonces, si es un código difícil en lugar de sizeof(...) , aquí encontrará un problema.

Espero que esto ayude.


Definitivamente sería más seguro hacer:

sizeof(foo) * SOME_NUM

Si copia su matriz de esta manera, debería usar

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

Esto siempre funcionará siempre que haya asignado pBuff al mismo tamaño. De esta forma, 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.





bit-packing