簡體   English   中英

如何確定編譯器是否在虛函數上使用早期綁定或后期綁定?

[英]How can I determine if a compiler uses early or late binding on a virtual function?

我有以下代碼:

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;
}

我被要求確定編譯器是否對最終函數調用使用早期綁定或后期綁定。 我在網上搜索過,但沒有找到任何幫助我。 有人能告訴我如何執行這項任務嗎?

您可以查看反匯編,看它是否通過vtable重定向。

線索是它是直接調用函數的地址(早期綁定)還是調用計算地址(后期綁定)。 另一種可能性是函數內聯,您可以考慮將其作為早期綁定。

當然,標准沒有規定實現細節,可能還有其他可能性,但這涵蓋了“正常”實現。

你總是可以使用hack:D

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

如果編譯器確實使用了vtbl指針,那么猜猜會發生什么:>

p3.speak()  // here

查看生成的代碼。 例如,在Visual Studio中,您可以設置斷點,然后右鍵單擊並選擇“轉到反匯編”。

它使用早期綁定。 你有一個P3類型的對象。 雖然它是具有虛函數定義的基類,但是類型是具體的並且在編譯時是已知的,因此它不必考慮將虛函數映射到派生類。

這與在Pet構造函數中調用speak()非常相似 - 即使在創建派生對象時,當基類構造函數執行時,對象的類型也是基類的類型,因此函數不會使用v-table ,它會調用基類型的版本。

基本上,早期綁定是編譯時綁定,后期綁定是運行時綁定。 運行時綁定僅用於編譯器在編譯時沒有足夠的類型信息來解析調用的情況。

事實上,編譯器沒有義務特別使用任何一個,只是為了確保調用正確的函數。 在這種情況下,你的對象是具體類型Pet ,所以只要調用Pet::speak ,編譯器就是“做正確的事”。

現在,鑒於編譯器可以靜態地查看對象的類型,我懷疑大多數編譯器會優化掉虛擬調用,但並不要求他們這樣做。

如果您想知道您的特定編譯器正在做什么,唯一的方法是查閱其文檔,源代碼或生成的反匯編。

我只想到一種在運行時告訴的方法,而不需要猜測。 您可以簡單地使用0覆蓋多態類的vptr,並查看是否調用了方法或是否出現了分段錯誤。 這就是我的例子:

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

其中Concrete: T意味着調用的虛擬成員函數T通過一個具體類型是成功的。 類似地, Pointer: T說,調用的成員函數T通過Base指針是成功的。


作為參考,這是我的測試程序:

#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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM