Quiero saber qué es una " clase base virtual " y qué significa.
Déjame mostrarte un ejemplo:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
Quiero saber qué es una " clase base virtual " y qué significa.
Déjame mostrarte un ejemplo:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
Respuestas:
Las clases base virtuales, usadas en herencia virtual, son una forma de prevenir que aparezcan múltiples "instancias" de una clase dada en una jerarquía de herencia cuando se usa herencia múltiple.
Considere el siguiente escenario:
class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};
La jerarquía de clases anterior da como resultado el "diamante temido" que se ve así:
A
/ \
B C
\ /
D
Una instancia de D estará compuesta por B, que incluye A, y C, que también incluye A. Por lo tanto, tiene dos "instancias" (a falta de una mejor expresión) de A.
Cuando tienes este escenario, tienes la posibilidad de ambigüedad. Qué sucede cuando haces esto:
D d;
d.Foo(); // is this B's Foo() or C's Foo() ??
La herencia virtual está ahí para resolver este problema. Cuando especifica virtual cuando hereda sus clases, le está diciendo al compilador que solo desea una única instancia.
class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};
Esto significa que solo hay una "instancia" de A incluida en la jerarquía. Por lo tanto
D d;
d.Foo(); // no longer ambiguous
Este es un mini resumen. Para obtener más información, lea esto y esto . Un buen ejemplo también está disponible aquí .
virtual
, el diseño del objeto se parece al diamante; y si no usamos virtual
, el diseño del objeto se ve como una estructura de árbol que contiene dos A
s
Como nota al margen, el problema con el Diamante temido es que la clase base está presente varias veces. Entonces, con una herencia regular, crees que tienes:
A
/ \
B C
\ /
D
Pero en el diseño de la memoria, tienes:
A A
| |
B C
\ /
D
Esto explica por qué cuando llama D::foo()
, tiene un problema de ambigüedad. Pero el verdadero problema viene cuando quieres usar una variable miembro de A
. Por ejemplo, digamos que tenemos:
class A
{
public :
foo() ;
int m_iValue ;
} ;
Cuando intente acceder m_iValue
desde D
, el compilador protestará, porque en la jerarquía verá dos m_iValue
, no uno. Y si modifica uno, digamos, B::m_iValue
(ese es el A::m_iValue
padre de B
), C::m_iValue
no será modificado (ese es el A::m_iValue
padre de C
).
Aquí es donde la herencia virtual es útil, ya que con ella, volverá a un verdadero diseño de diamante, no solo con un foo()
método, sino también uno y solo uno m_iValue
.
Imagina:
A
Tiene alguna característica básica.B
le agrega algún tipo de genial conjunto de datos (por ejemplo)C
agrega algunas características interesantes como un patrón de observación (por ejemplo, encendido m_iValue
).D
hereda de B
y C
, y por lo tanto de A
.Con una herencia normal, modificar m_iValue
desde D
es ambiguo y esto debe resolverse. Incluso si es así, hay dos m_iValues
dentro D
, por lo que será mejor que recuerdes eso y actualices los dos al mismo tiempo.
Con la herencia virtual, modificar m_iValue
desde D
está bien ... Pero ... Digamos que sí D
. A través de su C
interfaz, adjuntaste un observador. Y a través de su B
interfaz, actualiza la matriz genial, que tiene el efecto secundario de cambiar directamente m_iValue
...
Como el cambio m_iValue
se realiza directamente (sin usar un método de acceso virtual), C
no se llamará al observador que "escucha" , porque el código que implementa la escucha está dentro C
y B
no lo sabe ...
Si tiene un diamante en su jerarquía, significa que tiene un 95% de probabilidad de haber hecho algo mal con dicha jerarquía.
Explicar la herencia múltiple con bases virtuales requiere un conocimiento del modelo de objetos C ++. Y explicar el tema claramente es mejor hacerlo en un artículo y no en un cuadro de comentarios.
La mejor explicación legible que encontré que resolvió todas mis dudas sobre este tema fue este artículo: http://www.phpcompiler.org/articles/virtualinheritance.html
Realmente no necesitará leer nada más sobre el tema (a menos que sea un escritor compilador) después de leer eso ...
Una clase base virtual es una clase que no se puede instanciar: no se puede crear un objeto directo a partir de ella.
Creo que estás confundiendo dos cosas muy diferentes. La herencia virtual no es lo mismo que una clase abstracta. La herencia virtual modifica el comportamiento de las llamadas a funciones; a veces resuelve llamadas a funciones que de otro modo serían ambiguas, a veces difiere el manejo de llamadas a funciones a una clase distinta de la que cabría esperar en una herencia no virtual.
Me gustaría añadir a las amables aclaraciones de OJ.
La herencia virtual no viene sin precio. Al igual que con todas las cosas virtuales, obtienes un éxito de rendimiento. Hay una forma de evitar este éxito de rendimiento que posiblemente sea menos elegante.
En lugar de romper el diamante derivando virtualmente, puede agregar otra capa al diamante, para obtener algo como esto:
B
/ \
D11 D12
| |
D21 D22
\ /
DD
Ninguna de las clases hereda virtualmente, todas heredan públicamente. Las clases D21 y D22 ocultarán la función virtual f (), que es ambigua para DD, quizás declarando la función privada. Cada uno de ellos definiría una función de contenedor, f1 () y f2 () respectivamente, cada uno de los cuales llama a la clase local (privado) f (), resolviendo así los conflictos. La clase DD llama a f1 () si quiere D11 :: f () y f2 () si quiere D12 :: f (). Si define los contenedores en línea, probablemente obtendrá aproximadamente cero sobrecarga.
Por supuesto, si puede cambiar D11 y D12, puede hacer el mismo truco dentro de estas clases, pero a menudo ese no es el caso.
Además de lo que ya se ha dicho sobre la (s) herencia (s) múltiple (s) y virtual (es), hay un artículo muy interesante en el Dr Dobb's Journal: herencia múltiple considerada útil
Estás siendo un poco confuso. No sé si estás mezclando algunos conceptos.
No tiene una clase base virtual en su OP. Solo tienes una clase base.
Hiciste herencia virtual. Esto generalmente se usa en herencia múltiple para que múltiples clases derivadas usen los miembros de la clase base sin reproducirlos.
No se crea una instancia de una clase base con una función virtual pura. Esto requiere la sintaxis a la que llega Paul. Normalmente se usa para que las clases derivadas deban definir esas funciones.
No quiero explicarte más sobre esto porque no entiendo totalmente lo que estás preguntando.
Significa que una llamada a una función virtual se reenviará a la clase "correcta".
Preguntas frecuentes sobre C ++ Lite FTW.
En resumen, a menudo se usa en escenarios de herencia múltiple, donde se forma una jerarquía de "diamantes". La herencia virtual romperá la ambigüedad creada en la clase inferior, cuando se llama a la función en esa clase y la función debe resolverse en la clase D1 o D2 por encima de esa clase inferior. Ver el artículo de preguntas frecuentes para obtener un diagrama y detalles.
También se usa en la delegación hermana , una característica poderosa (aunque no para los débiles de corazón). Vea estas preguntas frecuentes.
Consulte también el Artículo 40 en la Eficaz tercera edición de C ++ (43 en la 2da edición).
Ejemplo de uso ejecutable de herencia de diamantes
Este ejemplo muestra cómo usar una clase base virtual en el escenario típico: para resolver la herencia de diamantes.
#include <cassert>
class A {
public:
A(){}
A(int i) : i(i) {}
int i;
virtual int f() = 0;
virtual int g() = 0;
virtual int h() = 0;
};
class B : public virtual A {
public:
B(int j) : j(j) {}
int j;
virtual int f() { return this->i + this->j; }
};
class C : public virtual A {
public:
C(int k) : k(k) {}
int k;
virtual int g() { return this->i + this->k; }
};
class D : public B, public C {
public:
D(int i, int j, int k) : A(i), B(j), C(k) {}
virtual int h() { return this->i + this->j + this->k; }
};
int main() {
D d = D(1, 2, 4);
assert(d.f() == 3);
assert(d.g() == 5);
assert(d.h() == 7);
}
assert(A::aDefault == 0);
desde la función principal me da un error de compilación: aDefault is not a member of A
usando gcc 5.4.0. ¿Qué se supone que debe hacer?
Las clases virtuales no son mismo que la herencia virtual. Clases virtuales que no puede crear instancias, la herencia virtual es algo completamente diferente.
Wikipedia lo describe mejor que yo. http://en.wikipedia.org/wiki/Virtual_inheritance
Con la herencia típica de 3 niveles sin herencia no virtual de diamante, cuando crea una instancia de un nuevo objeto más derivado, se llama a nuevo y el compilador resuelve el tamaño requerido para el objeto del tipo de clase y lo pasa a nuevo.
nuevo tiene una firma:
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
Y hace un llamado a malloc
, devolviendo el puntero vacío
Esto luego se pasa al constructor del objeto más derivado, que llamará inmediatamente al constructor del medio y luego el constructor del medio llamará inmediatamente al constructor base. La base luego almacena un puntero a su tabla virtual al comienzo del objeto y luego sus atributos después. Esto luego regresa al constructor del medio que almacenará su puntero de tabla virtual en la misma ubicación y luego sus atributos después de los atributos que el constructor base habría almacenado. Regresa al constructor más derivado, que almacena un puntero a su tabla virtual en la misma ubicación y luego sus atributos después de los atributos que el constructor central habría almacenado.
Debido a que el puntero de la tabla virtual se sobrescribe, el puntero de la tabla virtual termina siendo siempre una de las clases más derivadas. La virtualidad se propaga hacia la clase más derivada, por lo que si una función es virtual en la clase media, será virtual en la clase más derivada pero no en la clase base. Si convierte polimórficamente una instancia de la clase más derivada en un puntero a la clase base, el compilador no resolverá esto en una llamada indirecta a la tabla virtual y en su lugar llamará a la función directamente A::function()
. Si una función es virtual para el tipo al que la ha convertido, se resolverá con una llamada a la tabla virtual, que siempre será la de la clase más derivada. Si no es virtual para ese tipo, simplemente llamará Type::function()
y le pasará el puntero del objeto, se convertirá en Tipo.
En realidad, cuando digo puntero a su tabla virtual, en realidad siempre hay un desplazamiento de 16 en la tabla virtual.
vtable for Base:
.quad 0
.quad typeinfo for Base
.quad Base::CommonFunction()
.quad Base::VirtualFunction()
pointer is typically to the first function i.e.
mov edx, OFFSET FLAT:vtable for Base+16
virtual
no se requiere nuevamente en clases más derivadas si es virtual en una clase menos derivada porque se propaga. Pero se puede usar para mostrar que la función es de hecho una función virtual, sin tener que verificar las clases que hereda las definiciones de tipo.
override
es otro protector del compilador que dice que esta función está anulando algo y, si no es así, arroja un error del compilador.
= 0
significa que esta es una función abstracta
final
evita que una función virtual se implemente nuevamente en una clase más derivada y se asegurará de que la tabla virtual de la clase más derivada contenga la función final de esa clase.
= default
hace explícito en la documentación que el compilador usará la implementación predeterminada
= delete
dar un error del compilador si se intenta una llamada a esto
Considerar
class Base
{
int a = 1;
int b = 2;
public:
void virtual CommonFunction(){} ;
void virtual VirtualFunction(){} ;
};
class DerivedClass1: virtual public Base
{
int c = 3;
public:
void virtual DerivedCommonFunction(){} ;
void virtual VirtualFunction(){} ;
};
class DerivedClass2 : virtual public Base
{
int d = 4;
public:
//void virtual DerivedCommonFunction(){} ;
void virtual VirtualFunction(){} ;
void virtual DerivedCommonFunction2(){} ;
};
class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
{
int e = 5;
public:
void virtual DerivedDerivedCommonFunction(){} ;
void virtual VirtualFunction(){} ;
};
int main () {
DerivedDerivedClass* d = new DerivedDerivedClass;
d->VirtualFunction();
d->DerivedCommonFunction();
d->DerivedCommonFunction2();
d->DerivedDerivedCommonFunction();
((DerivedClass2*)d)->DerivedCommonFunction2();
((Base*)d)->VirtualFunction();
}
Sin heredar virtualmente la clase de bajo, obtendrá un objeto que se ve así:
En lugar de esto:
Es decir, habrá 2 objetos base.
En la situación de herencia virtual de diamantes anterior, después de llamar a new, llama al constructor más derivado y en ese constructor, llama a los 3 constructores derivados que pasan compensaciones a su tabla de tabla virtual, en lugar de llamar simplemente llamando DerivedClass1::DerivedClass1()
yDerivedClass2::DerivedClass2()
y luego los que llama tantoBase::Base()
Lo siguiente está todo compilado en modo de depuración -O0, por lo que habrá un ensamblaje redundante
main:
.LFB8:
push rbp
mov rbp, rsp
push rbx
sub rsp, 24
mov edi, 48 //pass size to new
call operator new(unsigned long) //call new
mov rbx, rax //move the address of the allocation to rbx
mov rdi, rbx //move it to rdi i.e. pass to the call
call DerivedDerivedClass::DerivedDerivedClass() [complete object constructor] //construct on this address
mov QWORD PTR [rbp-24], rbx //store the address of the object on the stack as d
DerivedDerivedClass::DerivedDerivedClass() [complete object constructor]:
.LFB20:
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
.LBB5:
mov rax, QWORD PTR [rbp-8] // object address now in rax
add rax, 32 //increment address by 32
mov rdi, rax // move object address+32 to rdi i.e. pass to call
call Base::Base() [base object constructor]
mov rax, QWORD PTR [rbp-8] //move object address to rax
mov edx, OFFSET FLAT:VTT for DerivedDerivedClass+8 //move address of VTT+8 to edx
mov rsi, rdx //pass VTT+8 address as 2nd parameter
mov rdi, rax //object address as first
call DerivedClass1::DerivedClass1() [base object constructor]
mov rax, QWORD PTR [rbp-8] //move object address to rax
add rax, 16 //increment object address by 16
mov edx, OFFSET FLAT:VTT for DerivedDerivedClass+24 //store address of VTT+24 in edx
mov rsi, rdx //pass address of VTT+24 as second parameter
mov rdi, rax //address of object as first
call DerivedClass2::DerivedClass2() [base object constructor]
mov edx, OFFSET FLAT:vtable for DerivedDerivedClass+24 //move this to edx
mov rax, QWORD PTR [rbp-8] // object address now in rax
mov QWORD PTR [rax], rdx. //store address of vtable for DerivedDerivedClass+24 at the start of the object
mov rax, QWORD PTR [rbp-8] // object address now in rax
add rax, 32 // increment object address by 32
mov edx, OFFSET FLAT:vtable for DerivedDerivedClass+120 //move this to edx
mov QWORD PTR [rax], rdx //store vtable for DerivedDerivedClass+120 at object+32 (Base)
mov edx, OFFSET FLAT:vtable for DerivedDerivedClass+72 //store this in edx
mov rax, QWORD PTR [rbp-8] //move object address to rax
mov QWORD PTR [rax+16], rdx //store vtable for DerivedDerivedClass+72 at object+16 (DerivedClass2)
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax+28], 5
.LBE5:
nop
leave
ret
Llama Base::Base()
con un puntero al desplazamiento de objeto 32. Base almacena un puntero a su tabla virtual en la dirección que recibe y sus miembros después.
Base::Base() [base object constructor]:
.LFB11:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi //stores address of object on stack (-O0)
.LBB2:
mov edx, OFFSET FLAT:vtable for Base+16 //puts vtable for Base+16 in edx
mov rax, QWORD PTR [rbp-8] //copies address of object from stack to rax
mov QWORD PTR [rax], rdx //stores it address of object
mov rax, QWORD PTR [rbp-8] //copies address of object on stack to rax again
mov DWORD PTR [rax+8], 1 //stores a = 1 in the object
mov rax, QWORD PTR [rbp-8] //junk from -O0
mov DWORD PTR [rax+12], 2 //stores b = 2 in the object
.LBE2:
nop
pop rbp
ret
DerivedDerivedClass::DerivedDerivedClass()
luego llama DerivedClass1::DerivedClass1()
con un puntero al objeto offset 0 y también pasa la dirección deVTT for DerivedDerivedClass+8
DerivedClass1::DerivedClass1() [base object constructor]:
.LFB14:
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi //address of object
mov QWORD PTR [rbp-16], rsi //address of VTT+8
.LBB3:
mov rax, QWORD PTR [rbp-16] //address of VTT+8 now in rax
mov rdx, QWORD PTR [rax] //address of DerivedClass1-in-DerivedDerivedClass+24 now in rdx
mov rax, QWORD PTR [rbp-8] //address of object now in rax
mov QWORD PTR [rax], rdx //store address of DerivedClass1-in-.. in the object
mov rax, QWORD PTR [rbp-8] // address of object now in rax
mov rax, QWORD PTR [rax] //address of DerivedClass1-in.. now implicitly in rax
sub rax, 24 //address of DerivedClass1-in-DerivedDerivedClass+0 now in rax
mov rax, QWORD PTR [rax] //value of 32 now in rax
mov rdx, rax // now in rdx
mov rax, QWORD PTR [rbp-8] //address of object now in rax
add rdx, rax //address of object+32 now in rdx
mov rax, QWORD PTR [rbp-16] //address of VTT+8 now in rax
mov rax, QWORD PTR [rax+8] //address of DerivedClass1-in-DerivedDerivedClass+72 (Base::CommonFunction()) now in rax
mov QWORD PTR [rdx], rax //store at address object+32 (offset to Base)
mov rax, QWORD PTR [rbp-8] //store address of object in rax, return
mov DWORD PTR [rax+8], 3 //store its attribute c = 3 in the object
.LBE3:
nop
pop rbp
ret
VTT for DerivedDerivedClass:
.quad vtable for DerivedDerivedClass+24
.quad construction vtable for DerivedClass1-in-DerivedDerivedClass+24
.quad construction vtable for DerivedClass1-in-DerivedDerivedClass+72
.quad construction vtable for DerivedClass2-in-DerivedDerivedClass+24
.quad construction vtable for DerivedClass2-in-DerivedDerivedClass+72
.quad vtable for DerivedDerivedClass+120
.quad vtable for DerivedDerivedClass+72
construction vtable for DerivedClass1-in-DerivedDerivedClass:
.quad 32
.quad 0
.quad typeinfo for DerivedClass1
.quad DerivedClass1::DerivedCommonFunction()
.quad DerivedClass1::VirtualFunction()
.quad -32
.quad 0
.quad -32
.quad typeinfo for DerivedClass1
.quad Base::CommonFunction()
.quad virtual thunk to DerivedClass1::VirtualFunction()
construction vtable for DerivedClass2-in-DerivedDerivedClass:
.quad 16
.quad 0
.quad typeinfo for DerivedClass2
.quad DerivedClass2::VirtualFunction()
.quad DerivedClass2::DerivedCommonFunction2()
.quad -16
.quad 0
.quad -16
.quad typeinfo for DerivedClass2
.quad Base::CommonFunction()
.quad virtual thunk to DerivedClass2::VirtualFunction()
vtable for DerivedDerivedClass:
.quad 32
.quad 0
.quad typeinfo for DerivedDerivedClass
.quad DerivedClass1::DerivedCommonFunction()
.quad DerivedDerivedClass::VirtualFunction()
.quad DerivedDerivedClass::DerivedDerivedCommonFunction()
.quad 16
.quad -16
.quad typeinfo for DerivedDerivedClass
.quad non-virtual thunk to DerivedDerivedClass::VirtualFunction()
.quad DerivedClass2::DerivedCommonFunction2()
.quad -32
.quad 0
.quad -32
.quad typeinfo for DerivedDerivedClass
.quad Base::CommonFunction()
.quad virtual thunk to DerivedDerivedClass::VirtualFunction()
virtual thunk to DerivedClass1::VirtualFunction():
mov r10, QWORD PTR [rdi]
add rdi, QWORD PTR [r10-32]
jmp .LTHUNK0
virtual thunk to DerivedClass2::VirtualFunction():
mov r10, QWORD PTR [rdi]
add rdi, QWORD PTR [r10-32]
jmp .LTHUNK1
virtual thunk to DerivedDerivedClass::VirtualFunction():
mov r10, QWORD PTR [rdi]
add rdi, QWORD PTR [r10-32]
jmp .LTHUNK2
non-virtual thunk to DerivedDerivedClass::VirtualFunction():
sub rdi, 16
jmp .LTHUNK3
.set .LTHUNK0,DerivedClass1::VirtualFunction()
.set .LTHUNK1,DerivedClass2::VirtualFunction()
.set .LTHUNK2,DerivedDerivedClass::VirtualFunction()
.set .LTHUNK3,DerivedDerivedClass::VirtualFunction()
DerivedDerivedClass::DerivedDerivedClass()
luego pasa la dirección del objeto + 16 y la dirección de VTT para DerivedDerivedClass+24
a DerivedClass2::DerivedClass2()
cuyo montaje es idéntica a DerivedClass1::DerivedClass1()
excepción de la línea mov DWORD PTR [rax+8], 3
que, obviamente, tiene un 4 en vez de 3 para d = 4
.
Después de esto, reemplaza los 3 punteros de la tabla virtual en el objeto con punteros para desplazar en DerivedDerivedClass
vtable a la representación para esa clase.
d->VirtualFunction();
:
mov rax, QWORD PTR [rbp-24] //store pointer to virtual table in rax
mov rax, QWORD PTR [rax] //dereference and store in rax
add rax, 8 // call the 2nd function in the table
mov rdx, QWORD PTR [rax] //dereference
mov rax, QWORD PTR [rbp-24]
mov rdi, rax
call rdx
d->DerivedCommonFunction();
:
mov rax, QWORD PTR [rbp-24]
mov rdx, QWORD PTR [rbp-24]
mov rdx, QWORD PTR [rdx]
mov rdx, QWORD PTR [rdx]
mov rdi, rax
call rdx
d->DerivedCommonFunction2();
:
mov rax, QWORD PTR [rbp-24]
lea rdx, [rax+16]
mov rax, QWORD PTR [rbp-24]
mov rax, QWORD PTR [rax+16]
add rax, 8
mov rax, QWORD PTR [rax]
mov rdi, rdx
call rax
d->DerivedDerivedCommonFunction();
:
mov rax, QWORD PTR [rbp-24]
mov rax, QWORD PTR [rax]
add rax, 16
mov rdx, QWORD PTR [rax]
mov rax, QWORD PTR [rbp-24]
mov rdi, rax
call rdx
((DerivedClass2*)d)->DerivedCommonFunction2();
:
cmp QWORD PTR [rbp-24], 0
je .L14
mov rax, QWORD PTR [rbp-24]
add rax, 16
jmp .L15
.L14:
mov eax, 0
.L15:
cmp QWORD PTR [rbp-24], 0
cmp QWORD PTR [rbp-24], 0
je .L18
mov rdx, QWORD PTR [rbp-24]
add rdx, 16
jmp .L19
.L18:
mov edx, 0
.L19:
mov rdx, QWORD PTR [rdx]
add rdx, 8
mov rdx, QWORD PTR [rdx]
mov rdi, rax
call rdx
((Base*)d)->VirtualFunction();
:
cmp QWORD PTR [rbp-24], 0
je .L20
mov rax, QWORD PTR [rbp-24]
mov rax, QWORD PTR [rax]
sub rax, 24
mov rax, QWORD PTR [rax]
mov rdx, rax
mov rax, QWORD PTR [rbp-24]
add rax, rdx
jmp .L21
.L20:
mov eax, 0
.L21:
cmp QWORD PTR [rbp-24], 0
cmp QWORD PTR [rbp-24], 0
je .L24
mov rdx, QWORD PTR [rbp-24]
mov rdx, QWORD PTR [rdx]
sub rdx, 24
mov rdx, QWORD PTR [rdx]
mov rcx, rdx
mov rdx, QWORD PTR [rbp-24]
add rdx, rcx
jmp .L25
.L24:
mov edx, 0
.L25:
mov rdx, QWORD PTR [rdx]
add rdx, 8
mov rdx, QWORD PTR [rdx]
mov rdi, rax
call rdx