Realmente hace muchas preguntas diferentes aquí, por lo que voy a hacer todo lo posible para responder a cada una por turno.
Primero quiere saber cómo están alineados los miembros de los datos. La alineación de miembros está definida por el compilador, pero debido a la forma en que las CPU tratan los datos desalineados, todos tienden a seguir la misma directriz
que las estructuras deben alinearse en función del miembro más restrictivo (que suele ser, aunque no siempre, el más grande). tipo intrínseco), y las estructuras siempre están alineadas de modo que los elementos de una matriz estén todos alineados de la misma manera.
Por ejemplo:
struct some_object
{
char c;
double d;
int i;
};
Este struct sería 24 bytes. Debido a que la clase contiene un doble, estará alineada con 8 bytes, lo que significa que el char será rellenado por 7 bytes, y el int se rellenará por 4 para asegurar que en una matriz de some_object, todos los elementos estén alineados en 8 bytes. En general, esto depende del compilador, aunque encontrará que para una arquitectura de procesador dada, la mayoría de los compiladores alinean los datos de la misma manera.
Lo segundo que mencionas son los miembros derivados de la clase. Ordenar y alinear clases derivadas es un poco doloroso. Las clases siguen individualmente las reglas que describí anteriormente para las estructuras, pero cuando empiezas a hablar sobre la herencia te metes en un terreno desordenado. Dadas las siguientes clases:
class base
{
int i;
};
class derived : public base // same for private inheritance
{
int k;
};
class derived2 : public derived
{
int l;
};
class derived3 : public derived, public derived2
{
int m;
};
class derived4 : public virtual base
{
int n;
};
class derived5 : public virtual base
{
int o;
};
class derived6 : public derived4, public derived5
{
int p;
};
el diseño de memoria para la base serían:
int i // base
el diseño de memoria para derivado sería:
int i // base
int k // derived
el diseño de memoria para derived2 sería:
int i // base
int k // derived
int l // derived2
El diseño de la memoria para derivado3 sería:
int i // base
int k // derived
int i // base
int k // derived
int l // derived2
int m // derived3
Puede notar que la base y los derivados aparecen cada uno aquí dos veces. Esa es la maravilla de la herencia múltiple.
Para evitar que tengamos herencia virtual.
el diseño de memoria para derived4 sería:
base* base_ptr // ptr to base object
int n // derived4
int i // base
el diseño de memoria para derived5 sería:
base* base_ptr // ptr to base object
int o // derived5
int i // base
el diseño de memoria para derived6 sería:
base* base_ptr // ptr to base object
int n // derived4
int o // derived5
int i // base
Usted Notará que los derivados 4, 5 y 6 tienen un puntero al objeto base. Esto es necesario para que al llamar a cualquiera de las funciones de la base tenga un objeto para pasar a esas funciones. Esta estructura depende del compilador porque no está especificada en la especificación del lenguaje, pero casi todos los compiladores la implementan de la misma manera.
Las cosas se vuelven más complicadas cuando empiezas a hablar de funciones virtuales, pero, de nuevo, la mayoría de los compiladores las implementan igual. Tome las siguientes clases:
class vbase
{
virtual void foo() {};
};
class vbase2
{
virtual void bar() {};
};
class vderived : public vbase
{
virtual void bar() {};
virtual void bar2() {};
};
class vderived2 : public vbase, public vbase2
{
};
Cada una de estas clases contiene al menos una función virtual.
el diseño de memoria para Vbase sería:
void* vfptr // vbase
el diseño de memoria para vbase2 sería:
void* vfptr // vbase2
el diseño de memoria para vderived sería:
void* vfptr // vderived
El el diseño de la memoria para vderived2 sería:
void* vfptr // vbase
void* vfptr // vbase2
Hay muchas cosas que las personas no entienden sobre cómo funcionan los vftables. Lo primero que debe entender es que las clases solo almacenan punteros a vftables, no a vftables completos.
Lo que eso significa es que no importa cuántas funciones virtuales tenga una clase, solo tendrá una variable virtual, a menos que herede una variable virtual de otra parte a través de herencia múltiple. Casi todos los compiladores colocan el puntero vftable ante el resto de los miembros de la clase. Eso significa que puede tener algo de relleno entre el puntero de vftable y los miembros de la clase.
También puedo decirle que casi todos los compiladores implementan las capacidades del paquete pragma que le permiten forzar manualmente la alineación de la estructura. En general, no quieres hacer eso a menos que realmente sepas lo que estás haciendo, pero está allí, y algunas veces es necisario.
Lo último que ha preguntado es si puede controlar el pedido. Siempre controlas el orden. El compilador siempre ordenará las cosas en el orden en que las escriba. Espero que esta explicación larga alcance todo lo que necesita saber.
relacionada: http://stackoverflow.com/questions/2006216/why-is-data-structure-alignment-important-for-performance – jldupont
Recuerde que el tamaño de una estructura (o clase) puede no será igual a la suma del tamaño de sus miembros, principalmente porque los compiladores pueden agregar relleno entre los miembros. Un método más robusto es leer datos empaquetados en un búfer 'unsigned char', luego cargar los miembros de ese búfer. En similar, escribe miembros en un búfer, el resultado es el búfer. Esto evitará que problemas de alineación o embalaje causen estragos en su programa. –
no me preocupa el relleno, el relleno está bien, solo quiero poder predecir el formato de datos sin procesar de una estructura simple que es descendente de otras estructuras simples – Mat