虚函数和vtable是如何实现的?
我们都知道C++中什么是虚函数,但是深层次上它们是如何实现的呢?
vtable可以在运行时修改甚至直接访问吗?
vtable 是针对所有类都存在,还是只针对那些至少具有一个虚函数的类?
抽象类是否至少有一个条目的函数指针为 NULL?
使用单个虚函数是否会减慢整个类的速度? 或者只调用虚拟函数? 如果虚拟函数实际上被覆盖或不被覆盖,速度是否会受到影响,或者只要它是虚拟的就没有影响。
We all know what virtual functions are in C++, but how are they implemented at a deep level?
Can the vtable be modified or even directly accessed at runtime?
Does the vtable exist for all classes, or only those that have at least one virtual function?
Do abstract classes simply have a NULL for the function pointer of at least one entry?
Does having a single virtual function slow down the whole class? Or only the call to the function that is virtual? And does the speed get affected if the virtual function is actually overwritten or not, or does this have no effect so long as it is virtual.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
深层次的虚函数是如何实现的?
来自“虚拟函数C++”:
vtable可以在运行时修改甚至直接访问吗?
总的来说,我相信答案是“不”。 您可以进行一些内存修改来查找 vtable,但您仍然不知道调用它的函数签名是什么样的。 您希望使用此功能(该语言支持的)实现的任何目标都应该可以实现,而无需直接访问 vtable 或在运行时修改它。 另请注意,C++ 语言规范没有指定需要 vtable - 但这是大多数编译器实现虚拟函数的方式。
vtable 是针对所有对象都存在,还是只针对那些至少具有一个虚函数的对象?
我相信这里的答案是“这取决于实现”,因为规范一开始就不需要 vtable。 然而,实际上,我相信所有现代编译器只会在类至少有 1 个虚拟函数时创建 vtable。 存在与 vtable 相关的空间开销以及与调用虚拟函数与非虚拟函数相关的时间开销。
抽象类是否至少有一个条目的函数指针为 NULL?
答案是语言规范未指定,因此取决于实现。 如果未定义(通常没有定义),则调用纯虚函数会导致未定义的行为 (ISO/IEC 14882:2003 10.4-2)。 实际上,它确实在 vtable 中为该函数分配了一个槽,但没有为其分配地址。 这使得 vtable 不完整,需要派生类来实现该功能并完成 vtable。 有些实现只是简单地在 vtable 条目中放置一个 NULL 指针; 其他实现放置一个指向虚拟方法的指针,该方法执行类似于断言的操作。
请注意,抽象类可以定义纯虚函数的实现,但只能使用限定 ID 语法来调用该函数(即,在方法名称中完全指定类,类似于从派生类)。 这样做是为了提供易于使用的默认实现,同时仍然要求派生类提供重写。
使用单个虚拟函数是否会减慢整个类的速度,或者仅减慢对虚拟函数的调用?
这已经超出了我的知识范围,所以如果我错了,请有人帮助我!
我相信只有类中的虚拟函数才会遇到与调用虚拟函数和非虚拟函数相关的时间性能损失。 无论哪种方式,类的空间开销都是存在的。 请注意,如果存在 vtable,则每个类只有 1 个,而不是每个对象都有一个。
如果虚拟函数实际上被重写或不被重写,速度是否会受到影响,或者只要它是虚拟的就没有影响?
我不认为与调用基本虚拟函数相比,被重写的虚拟函数的执行时间不会减少。 但是,与为派生类和基类定义另一个 vtable 相关的类会产生额外的空间开销。
其他资源:
http: //www.codersource.net/published/view/325/virtual_functions_in.aspx(通过后台机器)
http://en.wikipedia.org/wiki/Virtual_table
http://www.codesourcery.com/public/cxx-abi/abi .html#vtable
How are virtual functions implemented at a deep level?
From "Virtual Functions in C++":
Can the vtable be modified or even directly accessed at runtime?
Universally, I believe the answer is "no". You could do some memory mangling to find the vtable but you still wouldn't know what the function signature looks like to call it. Anything that you would want to achieve with this ability (that the language supports) should be possible without access to the vtable directly or modifying it at runtime. Also note, the C++ language spec does not specify that vtables are required - however that is how most compilers implement virtual functions.
Does the vtable exist for all objects, or only those that have at least one virtual function?
I believe the answer here is "it depends on the implementation" since the spec doesn't require vtables in the first place. However, in practice, I believe all modern compilers only create a vtable if a class has at least 1 virtual function. There is a space overhead associated with the vtable and a time overhead associated with calling a virtual function vs a non-virtual function.
Do abstract classes simply have a NULL for the function pointer of at least one entry?
The answer is it is unspecified by the language spec so it depends on the implementation. Calling the pure virtual function results in undefined behavior if it is not defined (which it usually isn't) (ISO/IEC 14882:2003 10.4-2). In practice it does allocate a slot in the vtable for the function but does not assign an address to it. This leaves the vtable incomplete which requires the derived classes to implement the function and complete the vtable. Some implementations do simply place a NULL pointer in the vtable entry; other implementations place a pointer to a dummy method that does something similar to an assertion.
Note that an abstract class can define an implementation for a pure virtual function, but that function can only be called with a qualified-id syntax (ie., fully specifying the class in the method name, similar to calling a base class method from a derived class). This is done to provide an easy to use default implementation, while still requiring that a derived class provide an override.
Does having a single virtual function slow down the whole class or only the call to the function that is virtual?
This is getting to the edge of my knowledge, so someone please help me out here if I'm wrong!
I believe that only the functions that are virtual in the class experience the time performance hit related to calling a virtual function vs. a non-virtual function. The space overhead for the class is there either way. Note that if there is a vtable, there is only 1 per class, not one per object.
Does the speed get affected if the virtual function is actually overridden or not, or does this have no effect so long as it is virtual?
I don't believe the execution time of a virtual function that is overridden decreases compared to calling the base virtual function. However, there is an additional space overhead for the class associated with defining another vtable for the derived class vs the base class.
Additional Resources:
http://www.codersource.net/published/view/325/virtual_functions_in.aspx (via way back machine)
http://en.wikipedia.org/wiki/Virtual_table
http://www.codesourcery.com/public/cxx-abi/abi.html#vtable
不可移植,但如果您不介意肮脏的把戏,当然可以!
在我见过的大多数编译器中,vtbl * 是对象的前 4 个字节,而 vtbl 内容只是一个成员指针数组(通常按照它们的顺序)声明,与基类的第一个)。 当然还有其他可能的布局,但这就是我通常观察到的。
现在要搞一些恶作剧...
在运行时更改类:
替换所有实例的方法(猴子修补类)
这有点棘手,因为 vtbl 本身可能位于只读内存中。
由于 mprotect 操作,后者很可能会让病毒检查程序和链接唤醒并引起注意。 在使用 NX 位的过程中,它很可能会失败。
Not portably, but if you don't mind dirty tricks, sure!
In most compilers I've seen, the vtbl * is the first 4 bytes of the object, and the vtbl contents are simply an array of member pointers there (generally in the order they were declared, with the base class's first). There are of course other possible layouts, but that's what I've generally observed.
Now to pull some shenanigans...
Changing class at runtime:
Replacing a method for all instances (monkeypatching a class)
This one's a little trickier, since the vtbl itself is probably in read-only memory.
The latter is rather likely to make virus-checkers and the link wake up and take notice, due to the mprotect manipulations. In a process using the NX bit it may well fail.
使用单个虚函数是否会减慢整个类的速度?
拥有虚函数会减慢整个类的速度,因为在处理此类的对象时,必须初始化、复制另一项数据……。 对于一个有六名左右成员的班级来说,差异应该可以忽略不计。 对于仅包含单个
char
成员或根本不包含任何成员的类,差异可能会很明显。除此之外,需要注意的是,并非每次对虚函数的调用都是虚函数调用。 如果您有一个已知类型的对象,编译器可以发出正常函数调用的代码,甚至可以内联所述函数(如果需要)。 只有当您通过可能指向基类的对象或某个派生类的对象的指针或引用进行多态调用时,您才需要 vtable 间接寻址并在性能方面付出代价。
无论函数是否被覆盖,硬件必须执行的步骤本质上是相同的。 从对象中读取vtable的地址,从适当的槽中检索函数指针,并通过指针调用函数。 就实际性能而言,分支预测可能会产生一些影响。 例如,如果大多数对象引用给定虚拟函数的相同实现,那么即使在检索指针之前,分支预测器也有可能正确预测要调用哪个函数。 但哪个函数是通用函数并不重要:它可能是大多数对象委托给未覆盖的基本情况,或者大多数对象属于同一子类,因此委托给相同的覆盖情况。
它们是如何深层次实施的?
我喜欢 jheriko 使用模拟实现来演示这一点的想法。 但我会使用 C 来实现类似于上面代码的东西,以便更容易看到底层。
父类Foo
派生类Bar
函数f执行虚函数调用
所以你可以看到,vtable只是内存中的一个静态块,主要包含函数指针。 多态类的每个对象都会指向与其动态类型对应的vtable。 这也使得 RTTI 和虚函数之间的联系更加清晰:您可以通过查看类指向的 vtable 来检查它的类型。 上面的内容在很多方面都得到了简化,例如多重继承,但总体概念是合理的。
如果
arg
是Foo*
类型,并且您采用arg->vtable
,但实际上是Bar
类型的对象code>,那么您仍然可以获得vtable
的正确地址。 这是因为vtable
始终是对象地址处的第一个元素,无论它是以正确的方式调用vtable
还是base.vtable
- 键入的表达式。Does having a single virtual function slow down the whole class?
Having virtual functions slows down the whole class insofar as one more item of data has to be initialized, copied, … when dealing with an object of such a class. For a class with half a dozen members or so, the difference should be neglible. For a class which just contains a single
char
member, or no members at all, the difference might be notable.Apart from that, it is important to note that not every call to a virtual function is a virtual function call. If you have an object of a known type, the compiler can emit code for a normal function invocation, and can even inline said function if it feels like it. It's only when you do polymorphic calls, via a pointer or reference which might point at an object of the base class or at an object of some derived class, that you need the vtable indirection and pay for it in terms of performance.
The steps the hardware has to take are essentially the same, no matter whether the function is overwritten or not. The address of the vtable is read from the object, the function pointer retrieved from the appropriate slot, and the function called by pointer. In terms of actual performance, branch predictions might have some impact. So for example, if most of your objects refer to the same implementation of a given virtual function, then there is some chance that the branch predictor will correctly predict which function to call even before the pointer has been retrieved. But it doesn't matter which function is the common one: it could be most objects delegating to the non-overwritten base case, or most objects belonging to the same subclass and therefore delegating to the same overwritten case.
how are they implemented at a deep level?
I like the idea of jheriko to demonstrate this using a mock implementation. But I'd use C to implement something akin to the code above, so that the low level is more easily seen.
parent class Foo
derived class Bar
function f performing virtual function call
So you can see, a vtable is just a static block in memory, mostly containing function pointers. Every object of a polymorphic class will point to the vtable corresponding to its dynamic type. This also makes the connection between RTTI and virtual functions clearer: you can check what type a class is simply by looking at what vtable it points at. The above is simplified in many ways, like e.g. multiple inheritance, but the general concept is sound.
If
arg
is of typeFoo*
and you takearg->vtable
, but is actually an object of typeBar
, then you still get the correct address of thevtable
. That's because thevtable
is always the first element at the address of the object, no matter whether it's calledvtable
orbase.vtable
in a correctly-typed expression.通常使用 VTable,即指向函数的指针数组。
Usually with a VTable, an array of pointers to functions.
这是现代 C++ 中虚拟表的可运行手动实现。 它具有明确定义的语义,没有 hack,也没有
void*
。注意:
.*
和->*
与*
和->
是不同的运算符。 成员函数指针的工作方式不同。Here is a runnable manual implementation of virtual table in modern C++. It has well-defined semantics, no hacks and no
void*
.Note:
.*
and->*
are different operators than*
and->
. Member function pointers work differently.这个答案已被纳入社区维基解答
答案是未指定 - 调用纯虚函数结果如果未定义(通常没有定义),则处于未定义行为(ISO/IEC 14882:2003 10.4-2)。 有些实现只是简单地在 vtable 条目中放置一个 NULL 指针; 其他实现放置一个指向虚拟方法的指针,该方法执行类似于断言的操作。
请注意,抽象类可以定义纯虚函数的实现,但只能使用限定 ID 语法来调用该函数(即,在方法名称中完全指定类,类似于从派生类)。 这样做是为了提供易于使用的默认实现,同时仍然要求派生类提供重写。
This answer has been incorporated into the Community Wiki answer
The answer for that is that it is unspecified - calling the pure virtual function results in undefined behavior if it is not defined (which it usually isn't) (ISO/IEC 14882:2003 10.4-2). Some implementations do simply place a NULL pointer in the vtable entry; other implementations place a pointer to a dummy method that does something similar to an assertion.
Note that an abstract class can define an implementation for a pure virtual function, but that function can only be called with a qualified-id syntax (ie., fully specifying the class in the method name, similar to calling a base class method from a derived class). This is done to provide an easy to use default implementation, while still requiring that a derived class provide an override.
您可以使用函数指针作为类成员和静态函数作为实现,或者使用指向成员函数和成员函数的实现的指针来重新创建 C++ 中的虚函数的功能。 这两种方法之间只有符号上的优势......事实上,虚函数调用本身只是一种符号上的便利。 事实上继承只是一种符号上的方便……不使用继承的语言特性都可以实现。 :)
下面是未经测试的垃圾,可能有错误的代码,但希望能演示这个想法。
例如
You can recreate the functionality of virtual functions in C++ using function pointers as members of a class and static functions as the implementations, or using pointer to member functions and member functions for the implementations. There are only notational advantages between the two methods... in fact virtual function calls are just a notational convenience themselves. In fact inheritance is just a notational convenience... it can all be implemented without using the language features for inheritance. :)
The below is crap untested, probably buggy code, but hopefully demonstrates the idea.
e.g.
我会尽量让它变得简单:)
我们都知道 C++ 中的虚函数是什么,但是它们是如何在深层实现的呢?
这是一个带有函数指针的数组,函数是特定虚函数的实现。 该数组中的索引表示为类定义的虚拟函数的特定索引。 这包括纯虚函数。
当一个多态类派生另一个多态类时,我们可能会遇到以下情况:
可以在运行时修改甚至直接访问vtable吗?
不是标准方式 - 没有 API 可以访问它们。 编译器可能有一些扩展或私有 API 来访问它们,但这可能只是一个扩展。
vtable 是针对所有类都存在,还是只针对那些至少具有一个虚函数的类?
仅那些至少具有一个虚函数(甚至是析构函数)或派生至少一个具有其 vtable(“多态”)的类的类。
抽象类是否至少有一个条目的函数指针为 NULL?
这是一个可能的实现,但没有实践。 相反,通常有一个函数会打印“调用的纯虚函数”之类的内容,并执行
abort()
操作。 如果您尝试在构造函数或析构函数中调用抽象方法,则可能会发生对它的调用。使用单个虚函数是否会减慢整个类的速度? 或者只调用虚拟函数? 如果虚拟函数实际上被覆盖或不被覆盖,速度是否会受到影响,或者只要它是虚拟的就没有影响。
速度减慢仅取决于呼叫是解析为直接呼叫还是虚拟呼叫。 其他都不重要。 :)
如果你通过指针或对象的引用来调用虚函数,那么它总是会被实现为虚调用——因为编译器永远无法知道在运行时什么样的对象会被分配给这个指针,以及它是否是虚函数。是否重写此方法的类的名称。 只有在两种情况下,编译器才能将对虚拟函数的调用解析为直接调用:
final
(仅在 C++11 中)。 在这种情况下,编译器知道该方法不能进行任何进一步的重写,并且它只能是此类中的方法。请注意,虚拟调用仅具有取消引用两个指针的开销。 使用 RTTI(尽管仅适用于多态类)比调用虚拟方法慢,如果您找到用两种这样的方式实现相同事物的情况。 例如,定义 virtual bool HasHoof() { return false; } 然后仅重写为
bool Horse::HasHoof() { return true; }
将为您提供调用if (anim->HasHoof())
的能力,这比尝试if(dynamic_cast(anim)) 更快
。 这是因为在某些情况下,dynamic_cast
必须遍历类层次结构,甚至递归地查看是否可以从实际指针类型和所需的类类型构建路径。 虽然虚拟调用始终相同 - 取消引用两个指针。I'll try to make it simple :)
We all know what virtual functions are in C++, but how are they implemented at a deep level?
This is an array with pointers to functions, which are implementations of a particular virtual function. An index in this array represents particular index of a virtual function defined for a class. This includes pure virtual functions.
When a polymorphic class derives from another polymorphic class, we may have the following situations:
Can the vtable be modified or even directly accessed at runtime?
Not standard way - there's no API to access them. Compilers may have some extensions or private APIs to access them, but that may be only an extension.
Does the vtable exist for all classes, or only those that have at least one virtual function?
Only those that have at least one virtual function (be it even destructor) or derive at least one class that has its vtable ("is polymorphic").
Do abstract classes simply have a NULL for the function pointer of at least one entry?
That's a possible implementation, but rather not practiced. Instead there is usually a function that prints something like "pure virtual function called" and does
abort()
. The call to that may occur if you try to call the abstract method in the constructor or destructor.Does having a single virtual function slow down the whole class? Or only the call to the function that is virtual? And does the speed get affected if the virtual function is actually overwritten or not, or does this have no effect so long as it is virtual.
The slowdown is only dependent on whether the call is resolved as direct call or as a virtual call. And nothing else matters. :)
If you call a virtual function through a pointer or reference to an object, then it will be always implemented as virtual call - because the compiler can never know what kind of object will be assigned to this pointer in runtime, and whether it is of a class in which this method is overridden or not. Only in two cases the compiler can resolve the call to a virtual function as a direct call:
final
in the class to which you have a pointer or reference through which you call it (only in C++11). In this case compiler knows that this method cannot undergo any further overriding and it can only be the method from this class.Note though that virtual calls have only overhead of dereferencing two pointers. Using RTTI (although only available for polymorphic classes) is slower than calling virtual methods, should you find a case to implement the same thing two such ways. For example, defining
virtual bool HasHoof() { return false; }
and then override only asbool Horse::HasHoof() { return true; }
would provide you with ability to callif (anim->HasHoof())
that will be faster than tryingif(dynamic_cast<Horse*>(anim))
. This is becausedynamic_cast
has to walk through the class hierarchy in some cases even recursively to see if there can be built the path from the actual pointer type and the desired class type. While the virtual call is always the same - dereferencing two pointers.每个对象都有一个指向成员函数数组的 vtable 指针。
Each object has a vtable pointer that points to an array of member functions.
所有这些答案中都没有提到的是,在多重继承的情况下,基类都有虚拟方法。 继承类有多个指向 vmt 的指针。
结果是此类对象的每个实例的大小都更大。
大家都知道,具有虚方法的类有 4 个字节的额外空间用于 vmt,但在多重继承的情况下,对于每个具有虚方法的基类来说,它是 4 倍。4 是指针的大小。
Something not mentioned here in all these answers is that in case of multiple inheritance, where the base classes all have virtual methods. The inheriting class has multiple pointers to a vmt.
The result is that the size of each instance of such an object is bigger.
Everybody knows that a class with virtual methods has 4 bytes extra for the vmt, but in case of multiple inheritance it is for each base class that has virtual methods times 4. 4 being the size of the pointer.
Burly 的答案在这里是正确的,除了以下问题:
抽象类是否至少有一个条目的函数指针为 NULL?
答案是根本没有为抽象类创建虚拟表。 没有必要,因为无法创建这些类的对象!
换句话说,如果我们有:
通过pB访问的vtbl指针将是D类的vtbl。这正是多态性的实现方式。 也就是如何通过pB访问D方法。 B 类不需要 vtbl。
响应下面 Mike 的评论...
如果我描述中的 B 类有一个未被 D 覆盖的虚拟方法 foo() 和一个虚拟方法方法 bar() 被重写,那么 D 的 vtbl 将有一个指向 B 的 foo() 及其自己的 bar() 的指针。 仍然没有为 B 创建 vtbl。
Burly's answers are correct here except for the question:
Do abstract classes simply have a NULL for the function pointer of at least one entry?
The answer is that no virtual table is created at all for abstract classes. There is no need since no objects of these classes can be created!
In other words if we have:
The vtbl pointer accessed through pB will be the vtbl of class D. This is exactly how polymorphism is implemented. That is, how D methods are accessed through pB. There is no need for a vtbl for class B.
In response to Mike's comment below...
If the B class in my description has a virtual method foo() that is not overridden by D and a virtual method bar() that is overridden, then D's vtbl will have a pointer to B's foo() and to its own bar(). There is still no vtbl created for B.
我之前做了一个非常可爱的概念证明(看看继承顺序是否重要); 让我知道你的 C++ 实现是否实际上拒绝了它(我的 gcc 版本只给出了分配匿名结构的警告,但这是一个错误),我很好奇。
CCPolite.h:
CCPolite_constructor.h:
main.c:
输出:
注意,因为我从不分配我的假对象,所以不需要进行任何破坏; 析构函数会自动放置在动态分配对象的作用域末尾,以回收对象文字本身和 vtable 指针的内存。
very cute proof of concept i made a bit earlier(to see if order of inheritence matters); let me know if your implementation of C++ actually rejects it(my version of gcc only gives a warning for assigning anonymous structs, but that's a bug), i'm curious.
CCPolite.h:
CCPolite_constructor.h:
main.c:
output:
note since I am never allocating my fake object, there is no need to do any destruction; destructors are automatically put at the end of scope of dynamically allocated objects to reclaim the memory of the object literal itself and the vtable pointer.