c ++ alineación de datos / orden de miembros y herencia

¿Cómo se alinean / ordenan los miembros de datos si se usa herencia / herencia múltiple? ¿Es este comstackdor específico?

¿Hay alguna manera de especificar en una clase derivada cómo se ordenarán / alinearán los miembros (incluidos los miembros de la clase base)?

¡Gracias!

Realmente estás haciendo muchas preguntas diferentes aquí, así que haré mi mejor esfuerzo para responder a cada una por turno.

Primero, desea saber cómo están alineados los miembros de los datos. La alineación de miembros está definida por el comstackdor, pero debido a la forma en que las CPU se ocupan de los datos desalineados, todos tienden a seguir el mismo

establece que las estructuras deben alinearse en función del miembro más restrictivo (que suele ser, pero no siempre, el tipo intrínseco más grande), 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; }; 

Esta estructura sería de 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 comstackdor, aunque encontrará que para una architecture de procesador dada, la mayoría de los comstackdores alinean los datos de la misma manera.

Lo segundo que mencionas son 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 la memoria para la base sería:

 int i // base 

El diseño de la memoria derivada 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 memoria para derived3 sería:

 int i // base int k // derived int i // base int k // derived int l // derived2 int m // derived3 

Puede observar que la base y los derivados aparecen cada uno aquí dos veces. Esa es la maravilla de la herencia múltiple.

Para evitar eso, tenemos herencia virtual.

El diseño de memoria para derivado4 sería:

 base* base_ptr // ptr to base object int n // derived4 int i // base 

El diseño de memoria para derivado5 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 

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 comstackdor porque no está especificada en la especificación del lenguaje, pero casi todos los comstackdores la implementan de la misma manera.

Las cosas se vuelven más complicadas cuando comienzas a hablar de funciones virtuales, pero de nuevo, la mayoría de los comstackdores las implementan igual. Toma 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 la memoria para vbase sería:

 void* vfptr // vbase 

El diseño de la memoria para vbase2 sería:

 void* vfptr // vbase2 

El diseño de la memoria para vderived sería:

 void* vfptr // vderived 

El diseño de 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 comstackdores colocan el puntero vftable ante el rest 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 decirles que casi todos los comstackdores 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 preguntó es si puede controlar el pedido. Siempre controlas el orden. El comstackdor siempre ordenará las cosas en el orden en que las escriba. Espero que esta explicación larga scope todo lo que necesita saber.

No es solo un comstackdor específico: es probable que se vea afectado por las opciones del comstackdor. No conozco ningún comstackdor que le brinde un control minucioso sobre cómo se empaquetan y se ordenan los miembros y las bases con herencia múltiple.

Si está haciendo algo que depende del orden y el embalaje, intente almacenar una estructura POD dentro de su clase y usar eso.

Es específico del comstackdor.

Editar: básicamente se trata de dónde se coloca la tabla virtual y eso puede ser diferente según el comstackdor utilizado.

Tan pronto como su clase no sea POD (datos antiguos simples), todas las apuestas estarán desactivadas. Probablemente haya directivas específicas del comstackdor que puede usar para empaquetar / alinear datos.

Los comstackdores generalmente alinean los miembros de datos en las estructuras para permitir un acceso fácil. Esto significa que los elementos de datos normalmente comenzarán en los límites de las palabras y que las brechas normalmente se dejarán en una estructura para garantizar que los límites de las palabras no estén divididos.

asi que

 struct foo
 {
     char a;
     int b;
     char c;
 }

Normalmente ocupará más de 6 bytes para una máquina de 32 bits

La clase base normalmente se presenta primero y la clase derivada se establece después de la clase base. Esto permite que la dirección de la clase base sea igual a la dirección de la clase derivada.

En la herencia múltiple hay un desplazamiento entre la dirección de una clase y la dirección de la segunda clase base. >static_cast y dynamic_cast calcularán el desplazamiento. reinterpret_cast no. Los moldes de estilo C hacen un yeso estático si es posible, de lo contrario se vuelve a interpretar el reparto.

Como han mencionado otros, todo esto es específico del comstackdor, pero lo anterior debería brindarle una guía aproximada de lo que normalmente sucede.

El orden de los objetos en herencia múltiple no es siempre lo que usted especifica. Según lo que he experimentado, el comstackdor utilizará el orden especificado a menos que no pueda. No puede usar el orden especificado cuando la primera clase base no tiene funciones virtuales y otra clase base tiene funciones virtuales. En este caso, los primeros bytes de la clase tienen que ser un puntero de tabla de funciones virtuales, pero la primera clase base no tiene uno. El comstackdor reorganizará las clases base para que el primero tenga un puntero de tabla de funciones virtuales.

He probado esto con msdev y g ++ y ambos reorganizan las clases. Es molesto que parezcan tener reglas diferentes sobre cómo lo hacen. Si tiene 3 o más clases base y la primera no tiene funciones virtuales, estos comstackdores presentarán diferentes diseños.

Para estar seguro, elija dos y evite el otro.

  1. No confíe en el orden de las clases base cuando usa herencia múltiple.

  2. Al usar herencia múltiple, coloque todas las clases base con funciones virtuales antes de cualquier clase base sin funciones virtuales.

  3. Use 2 o menos clases base (ya que los comstackdores se reorganizan de la misma manera en este caso)

Todo el comstackdor que conozco pone el objeto de la clase base antes de los miembros de datos en un objeto de clase derivado. Los miembros de los datos están en orden como se indica en la statement de la clase. Puede haber lagunas debido a la alineación. Sin embargo, no digo que tenga que ser así.

Puedo responder una de las preguntas.

¿Cómo se alinean / ordenan los miembros de datos si se usa herencia / herencia múltiple?

Creé una herramienta para visualizar el diseño de la memoria de las clases, astackr cuadros de funciones y otra información ABI (Linux, GCC). Puede ver el resultado de la clase mysqlpp :: Connection (hereda OptionalExceptions) de la biblioteca MySQL ++ aquí .

enter image description here