「仮想基本クラス」について知りたい」とは何か、その意味。
例を示しましょう:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
「仮想基本クラス」について知りたい」とは何か、その意味。
例を示しましょう:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
回答:
仮想継承で使用される仮想基本クラスは、複数の継承を使用するときに、特定のクラスの複数の「インスタンス」が継承階層に表示されないようにする方法です。
次のシナリオを検討してください。
class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};
上記のクラス階層は、次のような「恐ろしいひし形」になります。
A
/ \
B C
\ /
D
Dのインスタンスは、Aを含むBと、Aを含むCで構成されます。したがって、Aの2つの「インスタンス」(より良い表現が必要)があります。
このシナリオがあると、あいまいになる可能性があります。これを行うとどうなりますか:
D d;
d.Foo(); // is this B's Foo() or C's Foo() ??
この問題を解決するために仮想継承があります。クラスを継承するときにvirtualを指定すると、単一のインスタンスのみが必要であることをコンパイラーに伝えます。
class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};
これは、Aの「インスタンス」が1つだけ階層に含まれていることを意味します。したがって
D d;
d.Foo(); // no longer ambiguous
virtual
と、オブジェクトのレイアウトがひし形のように見えることです。使用しない場合virtual
、オブジェクトのレイアウトは2つA
のs を含むツリー構造のように見えます
補足として、Dreaded Diamondの問題は、基本クラスが複数回存在することです。したがって、通常の継承では、次のように考えています。
A
/ \
B C
\ /
D
しかし、メモリレイアウトでは、次のようになります。
A A
| |
B C
\ /
D
これはD::foo()
、を呼び出すときに曖昧さの問題がある理由を説明しています。ただし、実際の問題は、のメンバー変数を使用するときに発生しますA
。たとえば、次のようなものだとします。
class A
{
public :
foo() ;
int m_iValue ;
} ;
あなたがアクセスしようとするでしょうときm_iValue
からのD
階層で、それは2表示されますので、コンパイラは、抗議しますm_iValue
、ではないものを。そして、たとえばB::m_iValue
(のA::m_iValue
親であるB
)をC::m_iValue
変更しても、変更されません(つまり、のA::m_iValue
親です)。C
)。
ここで、仮想継承が便利になります。仮想継承は、1つのfoo()
メソッドだけでなく1つだけの真のダイヤモンドレイアウトに戻りますm_iValue
。
想像してみてください:
A
いくつかの基本的な機能があります。B
それにある種のクールなデータ配列を追加します(例)C
オブザーバーパターン(たとえば、m_iValue
)のようないくつかのクールな機能を追加します。D
B
およびから継承されるC
ため、から継承されA
ます。通常の継承では、m_iValue
からの変更D
はあいまいであり、これを解決する必要があります。たとえあったとしても、中には2つm_iValues
ありますD
ので、それを覚えて、2つを同時に更新することをお勧めします。
仮想継承では、m_iValue
fromからの変更は問題D
ありません...しかし...あなたが持っているとしましょうD
。そのC
インターフェースを介して、オブザーバーを接続しました。そして、そのB
インターフェイスを介して、クールな配列を更新します。これには、直接変更するという副作用がありm_iValue
ます...
の変更はm_iValue
(仮想アクセサメソッドを使用せずに)直接行わC
れるため、リスニングを実装するコードがにありC
、B
それを認識していないため、「リスニング」スルーを介したオブザーバは呼び出されません...
階層にひし形がある場合は、その階層で何か問題があった可能性が95%あることを意味します。
仮想ベースを使用した多重継承について説明するには、C ++オブジェクトモデルの知識が必要です。また、トピックを明確に説明することは、コメントボックスではなく記事で行うのが最善です。
このテーマに関する私の疑問をすべて解決した、私が見つけた最良の読みやすい説明は、次の記事でした。 。http //www.phpcompiler.org/articles/virtualinheritance.html
それを読んだ後は、(コンパイラの作成者でない限り)トピックについて他に何も読む必要はありません...
仮想基本クラスはインスタンス化できないクラスです。そのクラスから直接オブジェクトを作成することはできません。
私はあなたが2つの非常に異なるものを混同していると思います。仮想継承は、抽象クラスと同じものではありません。仮想継承は、関数呼び出しの動作を変更します。場合によってはあいまいな関数呼び出しを解決することもあれば、非仮想継承で期待されるもの以外のクラスへの関数呼び出し処理を延期することもあります。
OJの親切な説明に追加したいと思います。
仮想継承には、代償が伴います。すべての仮想物と同様に、パフォーマンスに打撃を与えます。このパフォーマンスへの影響を回避する方法は、あまりエレガントではない可能性があります。
仮想的に派生してダイヤモンドを壊す代わりに、ダイヤモンドに別のレイヤーを追加して、次のようなものを得ることができます:
B
/ \
D11 D12
| |
D21 D22
\ /
DD
どのクラスも仮想的に継承せず、すべてパブリックに継承します。クラスD21とD22は、おそらく関数をプライベートとして宣言することによって、DDに対してあいまいな仮想関数f()を隠します。それらはそれぞれラッパー関数f1()とf2()を定義し、それぞれがクラスローカル(プライベート)f()を呼び出して、競合を解決します。クラスDDは、D11 :: f()が必要な場合はf1()を呼び出し、D12 :: f()が必要な場合はf2()を呼び出します。インラインでラッパーを定義すると、オーバーヘッドはほぼゼロになります。
もちろん、D11とD12を変更できる場合は、これらのクラス内で同じトリックを実行できますが、多くの場合そうではありません。
多重継承と仮想継承についてすでに述べられていることに加えて、ドブ博士のジャーナルに関する非常に興味深い記事があります:多重継承は有用と考えられます
あなたは少し混乱しています。いくつかの概念を混同しているかどうかはわかりません。
OPに仮想基本クラスがありません。あなただけの基本クラスがあります。
あなたは仮想継承を行いました。これは通常、複数の継承で使用されるため、複数の派生クラスは基本クラスのメンバーを複製せずに使用します。
純粋な仮想関数を持つ基本クラスはインスタンス化されません。これには、Paulが理解する構文が必要です。これは通常、派生クラスがこれらの関数を定義する必要があるために使用されます。
私はこれについてこれ以上説明したくありません。あなたが求めていることを完全には理解していないからです。
これは、仮想関数の呼び出しが「正しい」クラスに転送されることを意味します。
C ++ FAQ Lite FTW。
つまり、「ダイアモンド」階層が形成される複数継承シナリオでよく使用されます。次に、仮想継承は、そのクラスで関数を呼び出し、関数がその最下位クラスの上のクラスD1またはD2のいずれかに解決される必要がある場合、最下位クラスで作成されたあいまいさを解消します。FAQ項目を参照してください図と詳細をください。
強力な機能である姉妹委任でも使用されます(ただし、気弱な人には使用できません)。これを見て FAQをください。
また、Effective C ++第3版の項目40(第2版の43)も参照してください。
ダイヤモンド継承の実行可能な使用例
この例は、典型的なシナリオで仮想基本クラスを使用する方法を示しています:ダイヤモンドの継承を解決します。
#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);
メイン関数からのコンパイルエラー:aDefault is not a member of A
gcc 5.4.0の使用。何をするつもりですか?
仮想クラスがありません仮想継承と同じで。インスタンス化できない仮想クラス、仮想継承はまったく別のものです。
ウィキペディアは、私よりもよく説明しています。http://en.wikipedia.org/wiki/Virtual_inheritance
典型的な3レベルの非ダイヤモンド、非仮想継承の継承では、新しい最も派生したオブジェクトをインスタンス化すると、newが呼び出され、オブジェクトに必要なサイズがコンパイラーによってクラス型から解決され、newに渡されます。
newには署名があります:
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
そして、 malloc
、voidポインタを返します
次に、これは最も派生したオブジェクトのコンストラクターに渡されます。コンストラクターはすぐに中央のコンストラクターを呼び出し、次に中央のコンストラクターがすぐに基本コンストラクターを呼び出します。次に、ベースは、オブジェクトの開始時に仮想テーブルへのポインタを格納し、その後にその属性を格納します。次に、これは中央のコンストラクターに戻ります。中央のコンストラクターは、仮想テーブルポインターを同じ場所に格納し、その後、基本コンストラクターによって格納されたであろう属性の後にその属性を格納します。これは、最も派生したコンストラクターに戻ります。このコンストラクターは、仮想テーブルへのポインターを同じ場所に格納し、その後、中間のコンストラクターによって格納されたであろう属性の後にその属性を格納します。
仮想テーブルポインタが上書きされるため、仮想テーブルポインタは常に最も派生したクラスの1つになります。仮想性は最も派生したクラスに向かって伝播するため、関数が中間クラスで仮想である場合、関数は最も派生したクラスでは仮想になりますが、基本クラスでは仮想になります。最も派生したクラスのインスタンスを基本クラスへのポインターに多態的にキャストすると、コンパイラーはこれを仮想テーブルへの間接呼び出しに解決せず、代わりに関数を直接呼び出しますA::function()
。関数をキャストした型に対して仮想である場合、関数は、常に最も派生したクラスの仮想テーブルへの呼び出しに解決されます。そのタイプの仮想ではない場合、それは単に呼び出されますType::function()
、オブジェクトポインターをて渡し、Typeにキャストします。
実際、私がその仮想テーブルへのポインターを言うとき、それは実際には常に仮想テーブルへの16のオフセットです。
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
それが伝播するために、それがより少ない派生クラスで仮想である場合、より多くの派生クラスで再び必要とされません。ただし、この関数を使用して、関数が実際に仮想関数であることを示すことができます。継承するクラスの型定義を確認する必要はありません。
override
この関数は何かをオーバーライドしていて、そうでない場合はコンパイラエラーをスローすると言う別のコンパイラガードです。
= 0
これは抽象的な関数であることを意味します
final
より派生したクラスで仮想関数が再度実装されるのを防ぎ、最も派生したクラスの仮想テーブルにそのクラスの最終関数が含まれるようにします。
= default
ドキュメントでコンパイラがデフォルトの実装を使用することを明示します
= delete
これを呼び出そうとするとコンパイラエラーが発生する
検討する
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();
}
低音クラスを仮想的に継承しないと、次のようなオブジェクトが得られます。
これの代わりに:
つまり、2つの基本オブジェクトがあります。
仮想ダイヤモンド継承の状況で上記、新しいが呼び出された後、それはほとんどの派生コンストラクタを呼び出し、そのコンストラクタで、それはその仮想テーブルのテーブルにオフセットを通過するすべての3つの派生コンストラクタを呼び出して、だけではなく、呼び出しを呼び出すのDerivedClass1::DerivedClass1()
とDerivedClass2::DerivedClass2()
、それらの両方の呼び出しをして、Base::Base()
以下はすべてデバッグモード-O0でコンパイルされているため、冗長アセンブリがあります。
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
Base::Base()
オブジェクトオフセット32へのポインターを使用して呼び出します。Baseは、受け取ったアドレスとそのメンバーにある仮想テーブルへのポインターを格納します。
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()
次にDerivedClass1::DerivedClass1()
、オブジェクトオフセット0へのポインタで呼び出し、アドレスを渡しますVTT 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()
その後、オブジェクト+ 16のアドレスとのためのVTTのアドレスを通過DerivedDerivedClass+24
しDerivedClass2::DerivedClass2()
、そのアセンブリと同一であるDerivedClass1::DerivedClass1()
行を除いてmov DWORD PTR [rax+8], 3
、明らか4の代わりに3を有していますd = 4
。
この後、オブジェクト内の3つの仮想テーブルポインターすべてDerivedDerivedClass
を、そのクラスの表現へのvtable 内のオフセットへのポインターに置き換えます。
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