8

私は次のコードを持っています:

class Pet {
public:
  virtual string speak() const { return ""; }
};

class Dog : public Pet {
public:
  string speak() const { return "Bark!"; }
};

int main() {
  Dog ralph;
  Pet* p1 = &ralph;
  Pet& p2 = ralph;
  Pet p3;

  // Late binding for both:
  cout << "p1->speak() = " << p1->speak() <<endl;
  cout << "p2.speak() = " << p2.speak() << endl;

  // Early binding (probably):
  cout << "p3.speak() = " << p3.speak() << endl;
}

コンパイラが最終的な関数呼び出しにアーリーバインディングとレイトバインディングのどちらを使用するかを決定するように求められました。私はオンラインで検索しましたが、私を助けるものは何も見つかりませんでした。誰かが私がこのタスクを実行する方法を教えてもらえますか?

4

6 に答える 6

5

逆アセンブリを見て、vtableを介してリダイレクトされているように見えるかどうかを確認できます。

手がかりは、関数のアドレスを直接呼び出す(アーリーバインディング)か、計算されたアドレスを呼び出す(レイトバインディング)かです。もう1つの可能性は、関数がインライン化されていることです。これは、早期バインディングと見なすことができます。

もちろん、標準は実装の詳細を規定していません。他の可能性もあるかもしれませんが、それは「通常の」実装をカバーしています。

于 2011-09-30T13:42:12.560 に答える
3

あなたはいつでもハックを使うことができます:D

//...
Pet p3;
memset(&p3, 0, sizeof(p3));
//...

コンパイラがvtblポインタを使用する場合、何が起こるかを推測します:>

p3.speak()  // here
于 2011-09-30T14:34:07.270 に答える
2

生成されたコードを見てください。たとえば、Visual Studioでは、ブレークポイントを設定し、右クリックして[逆アセンブリに移動]を選択できます。

于 2011-09-30T13:39:13.700 に答える
2

初期バインディングを使用します。タイプP3のオブジェクトがあります。これは仮想関数定義を持つ基本クラスですが、型は具体的でコンパイル時に既知であるため、派生クラスへの仮想関数のマッピングを考慮する必要はありません。

これは、Petコンストラクターでspeak()を呼び出した場合とほとんど同じです。派生オブジェクトを作成する場合でも、基本クラスコンストラクターが実行しているときは、オブジェクトのタイプは基本のタイプであるため、関数はvテーブルを使用しません。 、基本タイプのバージョンを呼び出します。

基本的に、アーリーバインディングはコンパイル時バインディングであり、レイトバインディングはランタイムバインディングです。ランタイムバインディングは、コンパイラがコンパイル時に呼び出しを解決するのに十分な型情報を持っていない場合にのみ使用されます。

于 2011-09-30T13:39:23.750 に答える
1

実際、コンパイラには、適切な関数が呼び出されることを確認するためだけに、特にどちらかを使用する義務はありません。この場合、オブジェクトは具象型であり、コンパイラが「正しいことをしている」と呼ばれてPetいる限りです。Pet::speak

さて、コンパイラーがオブジェクトのタイプを静的に見ることができることを考えると、ほとんどのコンパイラーは仮想呼び出しを最適化すると思いますが、そうする必要はありません。

特定のコンパイラが何をしているのかを知りたい場合は、そのドキュメント、ソースコード、または生成された逆アセンブリを参照するしかありません。

于 2011-09-30T13:57:01.313 に答える
0

当て推量を使わずに、実行時に伝える方法を考えました。ポリモーフィッククラスのvptrを0でオーバーライドするだけで、メソッドが呼び出されるかどうか、またはセグメンテーション違反が発生するかどうかを確認できます。これは私の例で得られるものです:

Concrete: Base
Concrete: Derived
Pointer: Base
Pointer: Derived
DELETING VPTR!
Concrete: Base
Concrete: Derived
Segmentation fault

whereは、具象型を介したConcrete: T仮想メンバー関数の呼び出しが成功したことを意味します。T同様に、ポインタを介してPointer: Tのメンバー関数の呼び出しが成功したと言います。TBase


参考までに、これは私のテストプログラムです。

#include <iostream>
#include <string.h>

struct Base {
  unsigned x;
  Base() : x(0xEFBEADDEu) {
  }
  virtual void foo() const {
    std::cout << "Base" << std::endl;
  }
};

struct Derived : Base {
  unsigned y;
  Derived() : Base(), y(0xEFCDAB89u) {
  }
  void foo() const {
    std::cout << "Derived" << std::endl;
  }
};

template <typename T>
void dump(T* p) {
  for (unsigned i = 0; i < sizeof(T); i++) {
    std::cout << std::hex << (unsigned)(reinterpret_cast<unsigned char*>(p)[i]);
  }
  std::cout << std::endl;
}

void callfoo(Base* b) {
  b->foo();
}

int main() {
  Base b;
  Derived d;
  dump(&b);
  dump(&d);
  std::cout << "Concrete: ";
  b.foo();
  std::cout << "Concrete: ";
  d.foo();
  std::cout << "Pointer: ";
  callfoo(&b);
  std::cout << "Pointer: ";
  callfoo(&d);
  std::cout << "DELETING VPTR!" << std::endl;
  memset(&b,0,6);
  memset(&d,0,6);
  std::cout << "Concrete: ";
  b.foo();
  std::cout << "Concrete: ";
  d.foo();
  std::cout << "Pointer: ";
  callfoo(&b);
  std::cout << "Pointer: ";
  callfoo(&d);
  return 0;
}
于 2011-09-30T14:34:16.557 に答える