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


¿Cómo se alinean / ordenan los miembros de datos si se usa herencia / herencia múltiple? ¿Este compilador es específico?

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

Gracias!

Author: linuxbuild, 2010-01-05

8 answers

Realmente estás haciendo muchas preguntas diferentes aquí, así que voy a hacer mi mejor esfuerzo para responder cada una a su vez.

Primero desea saber cómo se alinean los miembros de datos. La alineación de miembros está definida por el compilador, pero debido a cómo las CPU tratan los datos desalineados, todos tienden a seguir la misma

Directriz de que las estructuras deben alinearse en función del miembro más restrictivo (que generalmente, pero no siempre, es el tipo intrínseco más grande), y las estructuras siempre son alineado de tal manera 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 con 7 bytes, y el int será rellenado con 4 para asegurar que en un array de some_object, todos los elementos estarán alineados con 8 bytes. En términos generales, esto depende del compilador, aunque encontrará que para una arquitectura de procesador dada, la mayoría de los compiladores alinean los datos lo mismo.

Lo segundo que mencionas son los miembros derivados de la clase. Ordenar y alinear las clases derivadas es un poco molesto. Las clases individualmente siguen las reglas que describí anteriormente para las estructuras, pero cuando empiezas a hablar de 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 base sería:

int i // base

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

int i // base
int k // derived

El diseño de memoria para derived2 sería be:

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 notar que base y derivada aparecen dos veces aquí. Esa es la maravilla de la herencia múltiple.

Para sortear que tenemos 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 4, 5, y 6 derivados todos tienen un puntero al objeto base. Esto es necissary por lo que cuando se llama a cualquiera de las funciones de base tiene 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 complican cuando se empieza a hablar de funciones virtuales, pero de nuevo, la mayoría de los compiladores las implementan de la misma manera. 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 diseño de memoria para vderived2 sería:

void* vfptr // vbase
void* vfptr // vbase2

Hay muchas cosas que la gente no entiende sobre cómo funcionan las vftables. Lo primero que hay que entender es que las clases solo almacenan punteros a vftables, no vftables completos.

Lo que eso significa es que no importa cuántas funciones virtuales tenga una clase, solo tendrá una vftable, a menos que herede una vftable de otro lugar a través de herencia múltiple. Casi todos los compiladores ponen el puntero vftable antes que el resto de los miembros de la clase. Eso significa que puede tener algún relleno entre el puntero 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 estructura alineacion. Generalmente no quieres hacer eso a menos que realmente sepas lo que estás haciendo, pero está ahí, y a veces es necesario.

Lo último que preguntó es si puede controlar el pedido. Siempre controlas los pedidos. El compilador siempre ordenará las cosas en el orden en que las escriba. Espero que esta larga explicación llegue a todo lo que necesitas saber.

 59
Author: Beanz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-05 17:48:54

No es solo específico del compilador, es probable que se vea afectado por las opciones del compilador. No estoy al tanto de ningún compilador que le dé un control detallado sobre cómo los miembros y las bases se empaquetan y ordenan con herencia múltiple.

Si estás haciendo algo que se basa en el orden y el embalaje, intenta almacenar una estructura de POD dentro de tu clase y usarla.

 3
Author: Joe Gauterin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-05 14:21:44

Es específico del compilador.

Editar: básicamente se trata de dónde se coloca la tabla virtual y eso puede ser diferente dependiendo del compilador que se utilice.

 1
Author: Goz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-05 14:17:30

Tan pronto como su clase no es POD (datos antiguos simples), todas las apuestas se cancelan. Probablemente hay directivas específicas del compilador que puede usar para empaquetar / alinear datos.

 1
Author: James,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-05 14:17:32

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

So

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

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

La clase base es normalmente mostrados primera y la clase derivada se plantea después de la clase base. Esto permite que la dirección de la clase base 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 lo hace. Los moldes de estilo C hacen un molde estático si es posible, de lo contrario un molde reinterpretado.

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

 1
Author: doron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-05 14:27:06

El orden de los objetos en herencia múltiple no siempre es el que se especifica. Por lo que he experimentado, el compilador usará 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 tiene que ser un puntero de tabla de función virtual, pero la primera clase base no tiene uno. El compilador reorganizará las clases base para que el primero tiene un puntero de tabla de función virtual.

He probado esto con msdev y g++ y ambos reorganizan las clases. Molestamente, parecen tener reglas diferentes para cómo lo hacen. Si tiene 3 o más clases base y la primera no tiene funciones virtuales, estos compiladores obtendrá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 use múltiples herencia.

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

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

 1
Author: mccoyn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-27 19:17:34

Todos los compiladores que conozco ponen el objeto de clase base antes que los miembros de datos en un objeto de clase derivado. Los miembros de los datos están en orden como se indica en la declaración de clase. Puede haber huecos debido a la alineación. No estoy diciendo que tenga que ser así.

 0
Author: user231967,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-05 14:25:31

Puedo responder una de las preguntas.

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

He creado una herramienta para visualizar el diseño de memoria de clases, marcos de pila 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í.

introduzca la descripción de la imagen aquí

 0
Author: linuxbuild,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-12 21:05:59