如何确定编译器是否在虚函数上使用早期绑定或后期绑定?

时间:2011-09-30 13:34:11

标签: c++ compiler-construction late-binding early-binding

我有以下代码:

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

我被要求确定编译器是否对最终函数调用使用早期绑定或后期绑定。我在网上搜索过,但没有找到任何帮助我。有人能告诉我如何执行这项任务吗?

6 个答案:

答案 0 :(得分:5)

您可以查看反汇编,看看它是否通过vtable重定向。

线索是它是直接调用函数的地址(早期绑定)还是调用计算地址(后期绑定)。另一种可能性是函数是内联的,你可以认为它是早期绑定。

当然,标准没有规定实现细节,可能还有其他可能性,但这涵盖了“正常”实现。

答案 1 :(得分:3)

你总是可以使用hack:D

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

如果编译器确实使用了vtbl指针,那么猜猜会发生什么:&gt;

p3.speak()  // here

答案 2 :(得分:2)

查看生成的代码。例如。在Visual Studio中,您可以设置断点,然后右键单击并选择“转到反汇编”。

答案 3 :(得分:2)

它使用早期绑定。你有一个P3类型的对象。虽然它是一个带有虚函数定义的基类,但是类型是具体的并且在编译时是已知的,因此它不必考虑将虚函数映射到派生类。

这与在Pet构造函数中调用speak()非常相似 - 即使在创建派生对象时,当基类构造函数执行时,对象的类型也是基类的类型,因此函数不会使用v-table,它会调用基类型的版本。

基本上,早期绑定是编译时绑定,后期绑定是运行时绑定。运行时绑定仅用于编译器在编译时没有足够的类型信息来解析调用的情况。

答案 4 :(得分:1)

实际上编译器没有义务特别使用任何一个,只是为了确保调用正确的函数。在这种情况下,您的对象具有类型Pet的具体类型,因此只要调用Pet::speak,编译器就是“做正确的事情”。

现在,鉴于编译器可以静态地查看对象的类型,我怀疑大多数编译器会优化掉虚拟调用,但不要求他们这样做。

如果您想知道您的特定编译器正在做什么,唯一的方法是查阅其文档,源代码或生成的反汇编。

答案 5 :(得分:0)

我只想到一种在运行时告诉的方法,而不需要猜测。您可以简单地使用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;
}