関数、データ、コンパイラの実行期間


クラスは関数とデータの2つの側面、コンパイル期間と実行期間の2つの観点から見られます.
まずコンパイル期間は変数に基づいて(オブジェクトでもポインタでも)役割ドメインが決定されます.つまり、どの呼び出し文がコンパイルされるかは、実際に指すメモリの場所とは無関係になります.したがって、子オブジェクトを指す親ポインタが子関数を直接呼び出す文はコンパイルできません.逆に、親オブジェクトを指す子ポインタは、オブジェクトに属さない関数と数にアクセスできます.はい、もちろんこれは危険で、ほとんど必然的に境界を越えやすいですが、コンパイルすることができます.このような矛盾しているように見える場合は,コンパイル期間の特徴によって決まる.しかし、実行期間になると、メモリのことを本当に考えなければなりません.つまり、対象によって行動を決めなければなりません.多態のNxの点はコンパイル期間のアクセスを拡張することであり,コンパイル期間は親クラスの範囲にアクセスを固定するが,いくつかのメカニズムにより,サブクラス関数への正しいアクセスを実現し,正しい2字に注意する.一定の代価を払う必要があります.オフセットテーブル、ダミーテーブルが必要です.したがって、ポインタが存在するクラス特有の関数にアクセスする場合は、期間バインド関数アドレスを再コンパイルできますが、虚関数にアクセスすると実行期間に遅延します.これは、マルチステートを実現するために、虚関数にキーワードヒントが必要な理由かもしれません.したがって、非虚継承条件下では、コンパイラが親の関数アドレスエントリをバインドしているため、親ポインタが子オブジェクトの同名関数を上書きしていることは理解できます.
同時に、変数のタイプが実行期間の検索動作を決定することに注意してください.結局変数のタイプもこのクラスのメモリモデルを決定します.
次に、子ポインタで親オブジェクトを指す不合理な例を説明します.

classball

{

intnumber;

public:

ball()

{

number= 1;

}

virtualvoidname()

{

cout<<number<<" ";

cout<<"thisis a ball"<<endl;

}

};

classfootball:virtualpublicball

{

private:

intkind;

intnumber;

public:

football()

{

kind= 2;

number= 4;

}

virtualvoidname()

{

this->ball::name();

cout<<endl;

}

virtualvoidbrand()

{

}

};

intexperimentRuntime()

{

football*footBall;

ballballBase;

footBall= reinterpret_cast<football*>(&ballBase);

// printf("addressof fooball is %x
",footBall);

footBall->name();

footBall->brand();

return1;

}


非虚継承条件ではname関数が見つかり、brandでエラーが発生します.これは、オブジェクトの虚表にbrandという項目がないため、理解しやすいです.しかしfootball虚がballクラスを継承している場合、nameでエラーが発生し、アセンブリコードを表示すると分かりやすくなります.footBll(変数)は4バイトオフセットしてから関数を検索します.footballタイプではメモリモデルの虚関数ポインタのアドレスが開始位置ではなく、4バイトオフセットするのでエラーになります.虚継承でない場合、2つのクラスのメモリモデルでは虚関数ポインタが先頭にあるのでアクセスできます.
これは、サブクラスポインタで親オブジェクトを指すとthisポインタを調整する価値がないことを示しています.もちろん、メモリの大きいタイプがローカルタイプを指しているので、調整済みとしか思えません.調整後(コンパイラから見る)、以降のオフセットはポインタのタイプに基づいているため、この例のthisは4バイト先にオフセットする.親が子を指す場合も、thisポインタが親を指すオブジェクトを調整し、親のタイプに基づいてoffsetする.
データへのアクセスの実現を見ると、ILTテーブルなどのメカニズムによって関数の呼び出しを実現するのとは異なる.データアクセスのキーワードはoffset値であることは間違いなく、この値は1つのクラスにとって固定されているので、あるオブジェクトが内部のデータにアクセスする場合は、そのクラスのこのオフセット値を加えればよい.しかし、この値がいつ確定するかは考慮すべき問題です.変数がオブジェクトである場合、オブジェクトが存在するクラスは変更されないため、コンパイラで決定できることは間違いありません.しかし、変数がポインタである場合に問題があります.もしこのポインタが彼のderivedclassを指している場合、オフセット量は本クラスと同じですか?同じならもちろんコンパイル期間が決まっていますが、違うとだめです.いつ同じで、いつ違うの?単純な継承のみの場合、c++はサブクラスが親のデータを完全に保持しなければならないことを規定しているため、コンパイラが親データを連続的に保存する前提で(これもほとんどのコンパイラのやり方であり、もちろん効率も最適)では、オフセット量はコンパイル期間中に決定できます.ダミーポインタがあっても、親ポインタが子オブジェクトを指す場合は、子オブジェクトの先頭アドレスではなく、子オブジェクトの親オブジェクトの開始アドレスを指すことを忘れないでください.vcコンパイラでは、ダミーポインタがなければピン、非ダミー継承の親ポインタ変数の値は、子オブジェクトの先頭アドレスと同じですが、子クラスにダミー関数がある場合は4バイトオフセットします.すなわちBase*ptr=derivedClassobject;ptr-&object ==4.したがって,虚関数がある場合でも,ポインタのタイプに応じてデータのオフセットをコンパイラで算出することができる.しかし虚継承の場合は特殊です.B,C虚継承A,D虚継承B,C.では、BタイプのポインタがBクラスオブジェクトとDクラスオブジェクトを指す場合、Aのデータのoffsetは異なります.この時は運転期間算出まで遅らせる必要があります.これも多態の代価である.