2011-01-05 9 views
7

Estoy "jugando" con la herencia virtual en C++, y quiero saber cómo se presenta un objeto de clase. tengo esas tres clases:descifrando volcados de vtable

class A { 
private: 
    int a; 
public: 
    A() {this->a = 47;} 
    virtual void setInt(int x) {this->a = x;} 
    virtual int getInt() {return this->a;} 
    ~A() {this->a = 0;} 
}; 

class B { 
private: 
    int b; 
public: 
    B() {b = 48;} 
    virtual void setInt(int x) {this->b = x;} 
    virtual int getInt() {return this->b;} 
    ~B() {b = 0;} 
}; 

class C : public A, public B { 
private: 
    int c; 
public: 
    C() {c = 49;} 
    virtual void setInt(int x) {this->c = x;} 
    virtual int getInt() {return this->c;} 
    ~C() {c = 0;} 
}; 

(creo que son correctas: p)

Solía ​​-fdump-class-hierarchy con g ++, y tengo esta

Vtable for A 
A::_ZTV1A: 4u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& _ZTI1A) 
16 A::setInt 
24 A::getInt 

Class A 
    size=16 align=8 
    base size=12 base align=8 
A (0x10209fb60) 0 
    vptr=((& A::_ZTV1A) + 16u) 

Vtable for B 
B::_ZTV1B: 4u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& _ZTI1B) 
16 B::setInt 
24 B::getInt 

Class B 
    size=16 align=8 
    base size=12 base align=8 
B (0x1020eb230) 0 
    vptr=((& B::_ZTV1B) + 16u) 

Vtable for C 
C::_ZTV1C: 8u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& _ZTI1C) 
16 C::setInt 
24 C::getInt 
32 (int (*)(...))-0x00000000000000010 
40 (int (*)(...))(& _ZTI1C) 
48 C::_ZThn16_N1C6setIntEi 
56 C::_ZThn16_N1C6getIntEv 

Class C 
    size=32 align=8 
    base size=32 base align=8 
C (0x1020f5080) 0 
    vptr=((& C::_ZTV1C) + 16u) 
    A (0x1020ebd90) 0 
     primary-for C (0x1020f5080) 
    B (0x1020ebe00) 16 
     vptr=((& C::_ZTV1C) + 48u) 

Ahora qué diablos son aquellos (int (*)(...))-0x00000000000000010 y C::_ZThn16_N1C6setIntEi and (int (*)(...))0 ?? ¿Alguien puede explicar el tugurio?

Gracias.

+1

Es indefinido. Cada compilador (e incluso versiones del compilador) lo hacen de manera diferente. –

+0

Puede usar C++ filt para decodificar '_ZTI1C' Las demás son ubicaciones que probablemente se llenarán en alguna etapa posterior del compilador con punteros a función. –

Respuesta

6

No estoy 100% seguro de que esta respuesta sea correcta, pero esta es mi mejor estimación.

Cuando tiene una clase que hereda multiplicación y no virtualmente, el diseño de la clase suele ser un objeto completo del primer tipo base, luego un objeto completo del segundo tipo base, luego los datos para el objeto mismo . Si miras B, puedes ver el puntero vtable para el objeto A, y si miras a C puedes ver que hay punteros en el vtable para los objetos A y B.

Dado que los objetos se disponen de esta manera, significa que si tiene un puntero B* apuntando a un objeto C, el puntero en realidad no estará en la base del objeto; más bien estará apuntando a algún lugar en el medio. Esto significa que si alguna vez necesita lanzar el objeto a un A*, deberá ajustar el puntero B* para omitirlo al inicio del objeto. Para hacer esto, el compilador necesita codificar en algún lugar la cantidad de bytes que necesita saltar para llegar al inicio del objeto. Creo que el primer (int(*)(...)) es en realidad solo una cantidad bruta de bytes que debes mirar para llegar al inicio del objeto. Si se da cuenta, para el A vtable este puntero es 0 (dado que el vtable para A está al principio del objeto, y lo mismo es cierto para el vtable B (dado que también vive al comienzo del objeto. , observe que C vtable tiene dos partes: la primera parte es el vtable para A, y su primera entrada loca es cero (ya que si está en el vtable A, no necesita hacer ningún ajuste). Sin embargo, después de la primera mitad de esta tabla aparece lo que parece ser el vtable B, y observe que su primera entrada es el valor hexadecimal -0x10. Si observa el diseño de objetos C, notará que el puntero vtable B tiene 16 bytes después del A puntero vtable. Este -0x10 valor puede ser el desplazamiento correctivo que necesita para saltear el B puntero vtable para volver a la raíz del objeto.

La segunda entrada loca de cada vtable parece ser un puntero al vtable. Tenga en cuenta que siempre es igual a la dirección del objeto vtable (compare el nombre de la tabla de navegación y a qué apunta). Esto sería necesario si desea hacer algún tipo de identificación de tipo de tiempo de ejecución, ya que generalmente implica buscar en la dirección de la tabla de contenido (o al menos algo cerca de la parte frontal de la misma).

Por último, en cuanto a por qué hay del setInt críptica llamada y funciones getInt al final de la C vtable, estoy bastante seguro de que es porque el tipo C hereda dos conjuntos diferentes de funciones con nombre setInt y getInt - uno a través de A y uno a través de B.Si tuviera que adivinar, el problema aquí es garantizar que el compilador interno pueda diferenciar entre las dos funciones virtuales.

Espero que esto ayude!

+1

En el primer número '-0x10', también pensé en eso como el desplazamiento del subobjeto dentro del objeto final. Sobre por qué eso estaría allí ... No estoy del todo de acuerdo con su razonamiento, ya que el compilador está viendo todas las definiciones de clase cuando realiza los moldes (ya sean implícitos o explícitos), por lo que no es una pista para el compilador. Luego pensé en otras posibles razones, y lo único que se me ocurre es que al borrar un puntero a 'B', el compilador puede obtener un puntero al principio para liberar la memoria. Pero no estoy seguro de eso. –

+1

En las segundas entradas, lo más probable es que no sean punteros en los vtable, sino más bien punteros en el objeto typeinfo asociado a esa instancia particular. Tenga en cuenta los valores concretos: 'A :: _ ZTV1A' frente a' _ZTI1A', y que vptr en el objeto 'A' se establece en' (& A :: _ ZTV1A - 16u) '... no coinciden. –

+0

Esos son puntos muy buenos. Estoy bastante seguro de que estás en lo correcto en ambos aspectos. – templatetypedef

5

Aquí está tu volcado corrió a C++ filt:

Vtable for A 
A::vtable for A: 4u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& typeinfo for A) 
16 A::setInt 
24 A::getInt 

Class A 
    size=16 align=8 
    base size=12 base align=8 
A (0x10209fb60) 0 
    vptr=((& A::vtable for A) + 16u) 

Vtable for B 
B::vtable for B: 4u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& typeinfo for B) 
16 B::setInt 
24 B::getInt 

Class B 
    size=16 align=8 
    base size=12 base align=8 
B (0x1020eb230) 0 
    vptr=((& B::vtable for B) + 16u) 

Vtable for C 
C::vtable for C: 8u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& typeinfo for C) 
16 C::setInt 
24 C::getInt 
32 (int (*)(...))-0x00000000000000010 
40 (int (*)(...))(& typeinfo for C) 
48 C::non-virtual thunk to C::setInt(int) 
56 C::non-virtual thunk to C::getInt() 

Class C 
    size=32 align=8 
    base size=32 base align=8 
C (0x1020f5080) 0 
    vptr=((& C::vtable for C) + 16u) 
    A (0x1020ebd90) 0 
     primary-for C (0x1020f5080) 
    B (0x1020ebe00) 16 
     vptr=((& C::vtable for C) + 48u) 

Ni idea de lo que el (int (*)(...))-0x00000000000000010 y (int (*)(...))0 son.
La parte C::_ZThn16_N1C6setIntEi/C::non-virtual thunk to C::setInt(int) es una "optimización de llamadas a funciones virtuales en presencia de herencia múltiple o virtual" como se describe en here.

+0

+1 Me pegó :) Esta es la única parte de la pregunta que tenía clara: es simple si obtienes tanto el volcado de clase como el ensamblado: desplaza el puntero 'this' por 16 y salta a' setInt/getInt' (en cada caso) –

+0

@David: si alguna otra parte queda clara, agréguela a la respuesta. –

Cuestiones relacionadas