2011-12-29 15 views
11

Me gustaría saber exactamente cómo se organizarán las clases en la memoria esp. con herencia y funciones virtuales.diseño de memoria de la clase heredada

Sé que esto no está definido por el estándar de lenguaje C++. Sin embargo, ¿hay alguna manera fácil de descubrir cómo su compilador específico implementará estos mediante la escritura de algún código de prueba?

EDIT: - El uso de algunas de las respuestas a continuación: -

#include <iostream> 

using namespace std; 

class A { 
    public: 
    int a; 
    virtual void func() {} 
}; 

class B : public A { 
    public: 
    int b; 
    virtual void func() {} 
}; 

class C { 
    public: 
    int c; 
    virtual void func() {} 
}; 

class D : public A, public C { 
    public: 
    int d; 
    virtual void func() {} 
}; 

class E : public C, public A { 
    public: 
    int e; 
    virtual void func() {} 
}; 

class F : public A { 
    public: 
    int f; 
    virtual void func() {} 
}; 

class G : public B, public F { 
    public: 
    int g; 
    virtual void func() {} 
}; 

int main() { 
    A a; B b; C c; D d; E e; F f; G g; 
    cout<<"A: "<<(size_t)&a.a-(size_t)&a<<"\n"; 
    cout<<"B: "<<(size_t)&b.a-(size_t)&b<<" "<<(size_t)&b.b-(size_t)&b<<"\n"; 
    cout<<"C: "<<(size_t)&c.c-(size_t)&c<<"\n"; 
    cout<<"D: "<<(size_t)&d.a-(size_t)&d<<" "<<(size_t)&d.c-(size_t)&d<<" "<<(size_t)&d.d- (size_t)&d<<"\n"; 
    cout<<"E: "<<(size_t)&e.a-(size_t)&e<<" "<<(size_t)&e.c-(size_t)&e<<" "<<(size_t)&e.e- (size_t)&e<<"\n"; 
    cout<<"F: "<<(size_t)&f.a-(size_t)&f<<" "<<(size_t)&f.f-(size_t)&f<<"\n"; 
    cout<<"G: "<<(size_t)&g.B::a-(size_t)&g<<" "<<(size_t)&g.F::a-(size_t)&g<<" " <<(size_t)&g.b-(size_t)&g<<" "<<(size_t)&g.f-(size_t)&g<<" "<<(size_t)&g.g-(size_t)&g<<"\n"; 
} 

Y la salida es: -

A: 8 
B: 8 12 
C: 8 
D: 8 24 28 
E: 24 8 28 
F: 8 12 
G: 8 24 12 28 32 

Así que todas las clases tienen una v-PTR en loc 0 de tamaño 8 D tiene otro v-ptr en la ubicación 16. De manera similar, para E. G también parece tener un v-ptr a 16, aunque desde mi (limitado) entendimiento, habría supuesto que tendría más.

Respuesta

9

Una forma es imprimir los desplazamientos de todos los miembros:

class Parent{ 
public: 
    int a; 
    int b; 

    virtual void foo(){ 
     cout << "parent" << endl; 
    } 
}; 

class Child : public Parent{ 
public: 
    int c; 
    int d; 

    virtual void foo(){ 
     cout << "child" << endl; 
    } 
}; 

int main(){ 

    Parent p; 
    Child c; 

    p.foo(); 
    c.foo(); 

    cout << "Parent Offset a = " << (size_t)&p.a - (size_t)&p << endl; 
    cout << "Parent Offset b = " << (size_t)&p.b - (size_t)&p << endl; 

    cout << "Child Offset a = " << (size_t)&c.a - (size_t)&c << endl; 
    cout << "Child Offset b = " << (size_t)&c.b - (size_t)&c << endl; 
    cout << "Child Offset c = " << (size_t)&c.c - (size_t)&c << endl; 
    cout << "Child Offset d = " << (size_t)&c.d - (size_t)&c << endl; 

    system("pause"); 
} 

Salida:

parent 
child 
Parent Offset a = 8 
Parent Offset b = 12 
Child Offset a = 8 
Child Offset b = 12 
Child Offset c = 16 
Child Offset d = 20 

para que pueda ver todos los desplazamientos aquí. Notarás que no hay nada en el offset 0, ya que es presumiblemente donde va el puntero al vtable.

Observe también que los miembros heredados tienen las mismas compensaciones tanto en el elemento secundario como en el elemento primario.

+1

+1 Este código de ejemplo está más cerca de lo que se me ocurrió. Solo trata de evitar confiar en el diseño de la memoria. No hay garantía de que permanecerá igual en las versiones futuras de su compilador (o incluso en otros contextos con la misma versión del compilador, por ejemplo, la optimización). Apuesto que casi siempre hay una mejor manera de resolver el problema. – Andre

+0

Gracias. Eso ayuda un poco. Combinando tu respuesta con la de Azza ... También me interesó lo que sucedería si tuviéramos clase A; clase B; clase C: público A, público B; Esto parece dar un resultado donde hay múltiples punteros vtable. Los miembros de datos de A parecen ser lo primero antes de B. – owagh

+0

Nunca he tratado con herencias múltiples. Pero aún puedes intentarlo ver lo que muestran las compensaciones. Sinceramente, no tengo idea de cómo funciona la herencia múltiple debajo. – Mysticial

7

Visual Studio tiene al menos un hidden compiler option/d1reportSingleClassLayout (a partir de ~ 32: 00).

Uso: /d1reportSingleClassLayoutCLASSNAME donde no debe haber espacios en blanco entre el compilador y CLASSNAME (obviamente reemplace esto con el nombre de la clase que le interese).

+0

Oye, esto es genial y es exactamente lo que quería. ¿Hay funciones similares para otros compiladores como gcc, icc, etc.? – owagh

+0

@owagh: Lo siento, no lo sé. :/ – Xeo

+0

@owagh Para gcc, esto no parece ser un equivalente perfecto, pero tal vez todavía pueda ser útil: http://stackoverflow.com/questions/15951597/what-is-the-linux-aquivalent-to- msvcs-option-d1reportsingleclasslayout (short: compila en modo Debug con '-g' o' -ggdb', luego utiliza la utilidad 'pohole' en el archivo del objeto). –

0

La mejor manera es probablemente escribir unos pocos casos de prueba simples y luego compilarlos y depurarlos en el ensamblador (sin optimización): ejecutar una instrucción a la vez, verá dónde encaja todo.

Al menos así lo aprendí.

Y si encuentra un caso particularmente desafiante, publique en ¡SO!

0

Siempre y cuando se adhiera a la herencia única, los subobjetos generalmente se presentan en el orden en que se declaran. Se agrega un puntero al tipo de información en el frente, que es, por ejemplo, utilizado para despacho dinámico. Una vez que se genera la herencia múltiple, las cosas se vuelven más complejas, especialmente cuando se trata de una herencia virtual.

Para encontrar información precisa al menos para un sabor ABI, puede buscar el Itanium ABI. Esto documenta todos estos detalles. Se usa como C++ ABI al menos en algunas plataformas Linux (es decir, hay varios compiladores que pueden producir archivos objeto enlazados en un ejecutable).

Para determinar el diseño, simplemente imprima las direcciones de los subobjetos de un objeto give. Dicho esto, a menos que implementes un compilador, normalmente no importa. El único uso real del diseño de objetos que dudo es organizar a los miembros para minimizar el relleno.

+0

Seguramente no se agregará _vptr_ si no hay funciones 'virtuales'? Estoy tratando de obtener una respuesta concluyente sobre si la herencia única de PODs, sin nada 'virtual', dará como resultado un orden exacto de miembros, es decir' base.m1, base.m2, derived.m1, derived.m2'. Sé que puedo probarlo y ver si funciona en mi implementación, pero realmente estoy tratando de encontrar una forma de plataforma cruzada garantizada para hacer esto. (El fondo es que estoy mapeando en formatos binarios donde el orden es crítico). –

1

Cree un objeto de clase, moldee el puntero a él en la palabra de su máquina, use sizeof para encontrar el tamaño del objeto y examine la memoria en la ubicación. Algo como esto:

#include <iostream> 

class A 
{ 
public: 
    unsigned long long int mData; 
    A() : 
    mData(1) 
    { 
    }  
    virtual ~A() 
    { 
    } 
}; 
class B : public A 
{ 
public: 
    unsigned long long int mData1; 
    B() : 
    A(), mData1(2) 
    { 
    } 
}; 

int main(void) 
{ 
B lB; 

unsigned long long int * pB = (unsigned long long int *)(&lB); 

for(int i = 0; i < sizeof(B)/8; i++) 
{ 
    std::cout << *(pB + i) << std::endl; 
} 

return (0); 
} 


Program output (MSVC++ x86-64): 

5358814688 // vptr 
1   // A::mData 
2   // B::mData1 

En una nota lateral, Stanley B. Lippman tiene excelente libro "Inside the C++ Object Model".

Cuestiones relacionadas