如何确定编译器对虚拟函数使用早期绑定还是晚期绑定?
我有以下代码:
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;
}
我被要求确定编译器是否对最终函数调用使用早期或晚期绑定。我在网上搜索过但没有找到任何可以帮助我的东西。有人可以告诉我如何执行这项任务吗?
I have the following code:
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;
}
I have been asked to determine whether the compiler uses early or late binding for the final function call. I have searched online but have found nothing to help me. Can someone tell me how I would carry out this task?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
您可以查看反汇编,看看它是否看起来是通过 vtable 重定向的。
线索是它是直接调用函数的地址(早期绑定)还是调用计算的地址(后期绑定)。另一种可能性是该函数是内联的,您可以将其视为早期绑定。
当然,该标准没有规定实现细节,可能还有其他可能性,但这涵盖了“正常”实现。
You can look at the disassembly, to see whether it appears to be redirecting through a vtable.
The clue is whether it calls directly to the address of the function (early binding) or calls a computed address (late binding). The other possibility is that the function is inlined, which you can consider to be early binding.
Of course the standard doesn't dictate the implementation details, there may be other possibilities, but that covers "normal" implementations.
你总是可以使用 hack :D
如果编译器确实使用 vtbl 指针,猜猜会发生什么:>
You can always use hack :D
If compiler does use vtbl pointer, guess what will gonna happen :>
查看生成的代码。例如,在 Visual Studio 中,您可以设置断点,然后右键单击并选择“转到反汇编”。
Look at the generated code. E.g. in Visual Studio you can set a breakpoint, then right-click and select "Go To Disassembly".
它使用早期绑定。您有一个 P3 类型的对象。虽然它是具有虚函数定义的基类,但类型是具体的并且在编译时已知,因此不必考虑虚函数映射到派生类。
这与在 Pet 构造函数中调用 talk() 非常相似 - 即使在创建派生对象时,当基类构造函数执行时,对象的类型是基类的类型,因此该函数不会使用 v 表,它将调用基本类型的版本。
基本上,早期绑定是编译时绑定,后期绑定是运行时绑定。运行时绑定仅用于编译器在编译时没有足够的类型信息来解析调用的情况。
It uses early binding. You have an object of type P3. While it is a base class with a virtual function definition, the type is concrete and known at compile time, so it doesn't have to consider the virtual function mapping to derived classes.
This is much the same as if you called speak() in the Pet constructor - even when making derived objects, when the base class constructor is executing the type of the object is that of the base so the function would not use the v-table, it would call the base type's version.
Basically, early binding is compile time binding and late binding is run-time binding. Run time binding is only used in instances where the compiler doesn't have enough type information at compile time to resolve the call.
事实上,编译器没有义务特别使用其中任何一个,只是为了确保调用正确的函数。在本例中,您的对象属于具体类型
Pet
,因此只要调用Pet::speak
,编译器就会“做正确的事情”。现在,鉴于编译器可以静态地查看对象的类型,我怀疑大多数编译器都会优化虚拟调用,但并不要求它们这样做。
如果您想知道您的特定编译器正在做什么,唯一的方法是查阅其文档、源代码或生成的反汇编代码。
In fact the compiler has no obligation to use either one particularly, just to make sure that the right function is called. In this case, your object is of the concrete type
Pet
, so as long asPet::speak
is called the compiler is "doing the right thing".Now, given that the compiler can statically see the type of the object, I suspect that most compilers will optimize away the virtual call but there is no requirement that they do so.
If you want to know what your particular compiler is doing the only way is to consult its documentation, source code, or the generated disassembly.
我只是想到了一种在运行时判断的方法,无需猜测。您可以简单地用 0 覆盖多态类的 vptr,然后查看该方法是否被调用或者是否出现分段错误。这是我在示例中得到的结果:
其中
Concrete: T
表示通过具体类型调用T
的虚拟成员函数成功。类似地,Pointer: T
表示通过Base
指针调用T
的成员函数成功。作为参考,这是我的测试程序:
I just thought of a way to tell at runtime, without guesswork. You can simply override the vptr of your polymorphic classes with 0 and see if the method is called or if you get a segmentation fault. This is what I get for my example:
Where
Concrete: T
means that calling the virtual member function ofT
through a concrete type was successful. Analogously,Pointer: T
says that calling the member function ofT
through aBase
pointer was successful.For reference, this is my test program: