简体   繁体   English

upcasting和vtables如何协同工作以确保正确的动态绑定?

[英]How do upcasting and vtables work together to ensure correct dynamic binding?

So, vtable is a table maintained by the compiler which contains function pointers that point to the virtual functions in that class. 因此, vtable是由编译器维护的表,其中包含指向该类中的虚函数的函数指针。

and

Assigning a derived class's object to an ancestor class's object is called up-casting. 将派生类的对象分配给祖先类的对象称为向上转换。

Up-casting is handling a derived class instance/object using a base class pointer or reference; 向上转换使用基类指针或引用处理派生类实例/对象; the objects are not "assigned to", which implies an overwriting of value ala operator= invocation. 对象不是“赋值给”,这意味着覆盖值ala operator = invocation。
(Thanks to: Tony D ) (感谢: Tony D

Now, how it is known at run time "which" class's virtual function is supposed to be called? 现在,如何在运行时知道“哪个”类的虚函数应该被调用?

Which entry in vtable refers to the function of "particular" derived classes which is supposed to be called at run time? vtable中的哪个条目指的是应该在运行时调用的“特定”派生类的功能?

You can imagine (although the C++ specification doesn't say this) that the vtable is an identifier (or some other metadata that can be used to "find more information" about the class itself) and a list of functions. 您可以想象(尽管C ++规范没有说明这一点)vtable是一个标识符(或一些其他可用于“查找有关类本身的更多信息”的元数据)和一系列函数。

So, if we have a class like this: 所以,如果我们有这样一个类:

class Base
{
  public:
     virtual void func1();
     virtual void func2(int x);
     virtual std::string func3();
     virtual ~Base();
   ... some other stuff we don't care about ... 
};

The compiler will then produce a VTable something like this: 然后编译器将生成这样的VTable:

struct VTable_Base
{
   int identifier;
   void (*func1)(Base* this);
   void (*func2)(Base* this, int x);
   std::string (*func3)(Base* this); 
   ~Base(Base *this);
};

The compiler will then create an internal structure that, something like this (this is not possible to compile as C++, it's just to show what the compiler actually does - and I call it Sbase to differntiate the actual class Base ) 然后编译器将创建一个类似于此的内部结构(这不可能作为C ++编译,它只是为了显示编译器实际执行的操作 - 我称之为Sbase以区分实际的class Base

struct SBase
{
   VTable_Base* vtable;
   inline void func1(Base* this) { vtable->func1(this); }
   inline void func2(Base* this, int x) { vtable->func2(this, x); }
   inline std::string func3(Base* this) { return vtable->func3(this); }
   inline ~Base(Base* this) { vtable->~Base(this); }
};

It also builds the real vtable: 它还构建了真正的vtable:

VTable_Base vtable_base = 
{ 
   1234567, &Base::func1, &Base::func2, &Base::func3, &Base::~Base 
};

And in the constructor for Base , it will set the vtable = vtable_base; Base的构造函数中,它将设置vtable = vtable_base; .

When we then add a derived class, where we override one function (and by default, the destructor, even if we don't declare one) : 当我们添加派生类时,我们覆盖一个函数(默认情况下,析构函数,即使我们没有声明一个):

class Derived : public Base
{
    virtual void func2(int x) override; 
};

The compiler will now make this structure: 编译器现在将构建此结构:

struct VTable_Derived
{
   int identifier;
   void (*func1)(Base* this);
   void (*func2)(Base* this, int x);
   std::string (*func3)(Base* this); 
   ~Base(Derived *this);
};

and then does the same "structure" building: 然后做同样的“结构”建设:

struct SDerived
{
   VTable_Derived* vtable;
   inline void func1(Base* this) { vtable->func1(this); }
   inline void func2(Base* this, int x) { vtable->func2(this, x); }
   inline std::string func3(Base* this) { return vtable->func3(this); }
   inline ~Derived(Derived* this) { vtable->~Derived(this); }
};

We need this structure for when we are using Derived directly rather than through the Base class. 当我们直接使用Derived而不是通过Base类时,我们需要这个结构。

(We rely on the compiler chainin the ~Derived to call ~Base too, just like normal destructors that inherit) (我们依赖编译器链~Derived来调用~Base ,就像继承的普通析构函数一样)

And finally, we build an actual vtable: 最后,我们建立一个实际的vtable:

VTable_Derived vtable_derived = 
{ 
   7654339, &Base::func1, &Derived::func2, &Base::func3, &Derived::~Derived 
};

And again,the Derived constructor will set Dervied::vtable = vtable_derived for all instances. 同样, Derived构造Dervied::vtable = vtable_derived将为所有实例设置Dervied::vtable = vtable_derived

Edit to answer question in comments: The compiler has to carefully place the various components in both VTable_Derived and SDerived such that it matches VTable_Base and SBase , so that when we have a pointer to Base , the Base::vtable and Base::funcN() are matching Derived::vtable and Derived::FuncN . 编辑以回答评论中的问题:编译器必须小心地将各种组件放在VTable_DerivedSDerived ,以便它匹配VTable_BaseSBase ,这样当我们有一个指向Base的指针时, Base::vtableBase::funcN()匹配Derived::vtableDerived::FuncN If that doesn't match up, then the inheritance won't work. 如果不匹配,那么继承将不起作用。

If new virtual functions are added to Derived , they must then be placed after the ones inherited from Base . 如果将新虚拟函数添加到Derived ,则必须将它们放在从Base继承的函数之后。

End Edit. 结束编辑。

So, when we do: 所以,当我们这样做时:

Base* p = new Derived;

p->func2(); 

the code will look up SBase::Func2 , which will use the correct Derived::func2 (because the actual vtable inside p->vtable is VTable_Derived (as set by the Derived constructor that is called in conjunction with the new Derived ). 该代码将查找SBase::Func2 ,其将使用正确的Derived::func2 (因为实际的vtablep->vtableVTable_Derived (由设置Derived的构造被称为结合new Derived )。

I'll take a different route from the other answers and try to fill just the specific gaps in your knowledge, without going very much into the details. 我将采取与其他答案不同的路线,并尝试填补您的知识中的具体差距,而不必深入细节。 I'll address the mechanics just enough to help your understanding. 我会解决这些机制,足以帮助你理解。

So, vtable is a table maintained by the compiler which contains function pointers that point to the virtual functions in that class. 因此,vtable是由编译器维护的表,其中包含指向该类中的虚函数的函数指针。

The more precise way to say this is as follows: 更准确的说法如下:

Every class with virtual methods, including every class that inherits from a class with virtual methods, has its own virtual table. 每个具有虚方法的类,包括从具有虚方法的类继承的每个类,都有自己的虚拟表。 The virtual table of a class points to the virtual methods specific to that class, ie either inherited methods, overridden methods or newly added methods. 类的虚拟表指向特定于该类的虚方法,即继承方法,重写方法或新添加的方法。 Every instance of such a class contains a pointer to the virtual table that matches the class. 这样一个类的每个实例都包含一个指向与该类匹配的虚拟表的指针。

Up-casting is handling a derived class instance/object using a base class pointer or reference; 向上转换使用基类指针或引用处理派生类实例/对象; (...) (......)

Perhaps more enlightening: 也许更具启发性:

Up-casting means that a pointer or reference to an instance of class Derived is treated as if it were a pointer or reference to an instance of class Base . 向上转换意味着对Derived类的实例的指针或引用被视为指向类Base的实例的指针或引用。 The instance itself, however, is still purely an instance of Derived . 然而,实例本身仍然纯粹是Derived一个实例。

(When a pointer is "treated as a pointer to Base ", that means that the compiler generates code for dealing with a pointer to Base . In other words, the compiler and the generated code know no better than that they are dealing with a pointer to Base . Hence, a pointer that is "treated as" will have to point to an object that offers at least the same interface as instances of Base . This happens to be the case for Derived because of inheritance. We'll see how this works out below.) (当指针被“视为指向Base的指针”时,这意味着编译器生成用于处理指向Base的指针的代码。换句话说,编译器和生成的代码并不比它们处理指针更好到Base ,因此,被“当作”指针会指向一个对象,它至少提供了相同的接口的实例Base ,这恰好是对的情况下Derived因为继承的。我们将会看到这是如何在下面工作。)

At this point we can answer the first version of your question. 此时我们可以回答您问题的第一个版本。

Now, how it is known at run time "which" class's virtual function is supposed to be called? 现在,如何在运行时知道“哪个”类的虚函数应该被调用?

Suppose we have a pointer to an instance of Derived . 假设我们有一个指向Derived实例的指针。 First we upcast it, so it is treated as a pointer to an instance of Base . 首先我们将它上传,因此它被视为指向Base实例的指针。 Then we call a virtual method upon our upcasted pointer. 然后我们在我们的upcasted指针上调用一个虚方法。 Since the compiler knows that the method is virtual, it knows to look for the virtual table pointer in the instance. 由于编译器知道该方法是虚拟的,因此它知道在实例中查找虚拟表指针。 While we are treating the pointer as if it points to an instance of Base , the actual object has not changed value and the virtual table pointer within it is still pointing to the virtual table of Derived . 虽然我们将指针视为指向Base的实例,但实际对象没有更改值,并且其中的虚拟表指针仍指向Derived的虚拟表。 So at runtime, the address of the method is taken from the virtual table of Derived . 因此,在运行时,方法的地址取自Derived的虚拟表。

Now, the particular method may be inherited from Base or it might be overridden in Derived . 现在,特定方法可以从Base继承,也可以在Derived覆盖。 It does not matter; 不要紧; if inherited, the method pointer in the virtual table of Derived simply contains the same address as the corresponding method pointer in the virtual table of Base . 如果继承,则Derived虚拟表中的方法指针只包含与Base虚拟表中相应方法指针相同的地址。 In other words, both tables are pointing to the same method implementation for that particular method. 换句话说,两个表都指向该特定方法的相同方法实现。 If overridden, the method pointer in the virtual table of Derived differs from the corresponding method pointer in the virtual table of Base , so method lookups on instances of Derived will find the overridden method while lookups on instances of Base will find the original version of the method — regardless of whether a pointer to the instance is treated as a pointer to Base or a pointer to Derived . 如果被覆盖, Derived虚拟表中的方法指针与Base的虚拟表中的相应方法指针不同,因此Derived实例上的方法查找将找到重写方法,而对Base实例的查找将找到原始版本的方法 - 无论指向实例的指针是否被视为指向Base的指针或指向Derived的指针。

Finally, it should now be straightforward to explain why the second version of your question is a bit misguided: 最后,现在应该直截了当地解释为什么你的问题的第二个版本有点误导:

Which entry in vtable refers to the function of "particular" derived classes which is supposed to be called at run time? vtable中的哪个条目指的是应该在运行时调用的“特定”派生类的功能?

This question presupposes that vtable lookups are first by method and then by class. 这个问题预先假定vtable查找首先是方法,然后是类。 It is the other way round: first, the vtable pointer in the instance is used to find the vtable for the right class. 反过来说:首先,实例中的vtable指针用于查找正确类的vtable。 Then, the vtable for that class is used to find the right method. 然后,该类的vtable用于查找正确的方法。

Which entry in vtable refers to the function of "particular" derived classes which is supposed to be called at run time? vtable中的哪个条目指的是应该在运行时调用的“特定”派生类的功能?

None, it is not an entry in the vtable, but the vtable pointer that is part of each and every object instance that determines which are the correct set of virtual functions for that particular object. 无,它不是vtable中的条目,而是vtable指针,它是每个对象实例的一部分,用于确定哪个是该特定对象的正确虚函数集。 This way, depending on the actual vtable pointed to, invoking the "first virtual method" from the vtable may result in the calling of different functions for objects of different types in the same polymorphic hierarchy. 这样,根据指向的实际vtable,从vtable调用“第一虚拟方法”可能导致在同一多态层次结构中为不同类型的对象调用不同的函数。

Implementations may vary, but what I personally consider the most logical and performing thing to do is to have the vtable pointer being the first element in the class layout. 实现可能会有所不同,但我个人认为最合乎逻辑且最常执行的事情是将vtable指针作为类布局中的第一个元素。 This way you can dereference the very address of the object to determine its type based on the value of the pointer sitting in that address, since all objects of a given type will have that pointer pointing to the same vtable, which is created uniquely for every object that has virtual methods, which is required to enable features as overriding certain virtual methods. 这样,您可以取消引用对象的地址,以根据位于该地址中的指针的值确定其类型,因为给定类型的所有对象都将指向同一个vtable,该vtable是为每个vtable创建的具有虚拟方法的对象,这是启用功能以覆盖某些虚拟方法所必需的。

How do upcasting and vtables work together to ensure correct dynamic binding? upcasting和vtables如何协同工作以确保正确的动态绑定?

Upcasting itself isn't strictly needed, neither is downcasting. 不是严格要求向上倾斜,也不是向下倾斜。 Remember that you already have the object allocated in memory, and it will already have its vtable pointer set to the correct vtable for that type which is what ensures it, up an down casting doesn't change the vtable for that object, it only changes the pointer you operate through. 请记住,您已经在内存中分配了对象,并且它已经将其vtable指针设置为该类型的正确vtable,这样可以确保它,向下转换不会更改该对象的vtable,它只会更改你操作的指针。

Downcasting is needed when you want to access functionality that is not available in the base class and is declared in the derived class. 当您想要访问基类中不可用的功能并在派生类中声明时,需要向下转换。 But before you try to do that, you must be sure that particular object is of or inherits the type which declares that functionality, which is where dynamic_cast comes in, when you dynamic cast the compiler generates a check for that vtable entry and whether it inherits the requested type from another table, generated at compile time, and if so the dynamic cast succeeds, otherwise it fails. 但是在尝试执行此操作之前,必须确保特定对象是或继承声明该​​功能的类型,即dynamic_cast所在的类型,当动态转换编译器生成对该vtable条目的检查以及是否继承来自另一个表的请求类型,在编译时生成,如果是,则动态转换成功,否则失败。

The pointer you access the object through doesn't refer to the right set of virtual functions to call, it merely serves as a gauge to which functions in the vtable you can refer to as the developer. 您访问对象的指针并不是指要调用的正确的虚函数集,它仅用作衡量vtable中您可以称为开发人员的函数。 That is why it is safe to upcast using a C style or static cast, which performs no runtime checks, because then you only limit your gauge to the functions available in the base class, which are already available in the derived class, so there is no room for error and harm. 这就是为什么使用C样式或静态强制转换进行向上转换是安全的,它不执行运行时检查,因为这样你只能将量程限制为基类中可用的函数,这些函数已在派生类中可用,因此有没有错误和伤害的余地。 And that's why you must always use a dynamic cast or some other custom technique still based on virtual dispatch when you downcast, because you have to be sure that object's associated vtable does indeed contain the extra functionality you may invoke. 这就是为什么在向下转换时必须始终使用动态强制转换或其他一些仍基于虚拟分派的自定义技术,因为您必须确保对象的关联vtable确实包含您可能调用的额外功能。

Otherwise you will get undefined behavior, and of the "bad kind" at that, meaning something fatal will most likely happen, since interpreting arbitrary data as an address of a function of particular signature to be called is a very big no-no. 否则你会得到未定义的行为,以及那种“坏的”,这意味着最有可能发生致命的事情,因为将任意数据解释为要调用的特定签名函数的地址是一个非常大的禁忌。

Also note that in a static context, ie when it is known at compile time what the type is, the compiler will most likely not use the vtable to call virtual functions but use direct static calls or even inline certain functions, which will make them that much faster. 还要注意,在静态上下文中,即在编译时知道类型是什么时,编译器很可能不会使用vtable来调用虚函数,而是使用直接静态调用甚至内联某些函数,这将使它们成为快多了。 In such cases upcasting and using a base class pointer instead of the actual object will only diminish that optimization. 在这种情况下,向上转换并使用基类指针而不是实际对象只会减少该优化。

casting casting is a concept associated with variable. 铸造铸造是与变量相关的概念。 So any variable can be casted. 所以任何变量都可以铸造。 It can be casted up or down. 它可以向上或向下铸造。

char charVariable = 'A';
int intVariable = charVariable; // upcasting

int intVariable = 20;
char charVariale = intVariable; // downcasting 

for system defined data type Up cast or downcast is based on your current variable and it mainly related to how much memory compiler is allocating to both compared variable. 对于系统定义的数据类型向上转换或向下转换基于您当前的变量,它主要与内存编译器分配给两个比较变量的内存有关。

If you are assigning a variable which is allocating less memory than the type what is converting to, is called up cast. 如果要分配的变量分配的内存少于要转换的类型,则调用up cast。

If you are assigning a variable which is allocating more memory than the type what is converting to, is called down cast. 如果要分配的变量分配的内存多于转换的类型,则称为向下转换。 Down cast create some problem when the value is trying to cast can't fit in to that allocated memory area. 当试图转换的值无法适应分配的内存区域时,向下转换会产生一些问题。

Upcasting in Class level Just like system defined data type we can have object of base class and derived class. 类级别的上传与系统定义的数据类型一样,我们可以拥有基类和派生类的对象。 So if we want to convert derived type to base type , it is known as down upcasting. 因此,如果我们想将派生类型转换为基类型,则称为向下向上转换。 That can be achieved by pointer of a base class pointing to a derived class type. 这可以通过指向派生类类型的基类的指针来实现。

class Base{
    public:
        void display(){
          cout<<"Inside Base::display()"<<endl;
    }
};
class Derived:public Base{
    public:
      void display(){
           cout<<"Inside Derived::display()"<<endl;
    }
};

int main(){
   Base *baseTypePointer = new Derived(); // Upcasting
   baseTypePointer.display();  // because we have upcasted we want the out put as Derived::display() as output

}

output 产量

Inside Base::display() Inside Base :: display()

Excepted 除外

Inside Derived::display() 里面的Derived :: display()

In the above scenario the output wasn't as excepted. 在上面的场景中,输出并不是例外。 Its because we don't have the v-table and vptr (virtual pointer) in the object the base pointer will call the Base::display() though we have assigned derived type to the base pointer. 因为我们在对象中没有v-table和vptr(虚拟指针),所以基本指针将调用Base :: display(),尽管我们已将派生类型分配给基指针。

To avoid this problem c++ gives us virtual concept. 为了避免这个问题,c ++为我们提供了虚拟概念。 Now the base class display function need to be changed to a virtual type. 现在需要将基类显示功能更改为虚拟类型。

virtual void display()

full code is: 完整代码是:

class Base{
    public:
        virtual void display(){
          cout<<"Inside Base::display()"<<endl;
    }
};
class Derived:public Base{
    public:
      void display(){
           cout<<"Inside Derived::display()"<<endl;
    }
};

int main(){
   Base *baseTypePointer = new Derived(); // Upcasting
   baseTypePointer.display();  // because we have upcasted we want the out put as Derived::display() as output

}

output 产量

Inside Derived::display() 里面的Derived :: display()

Excepted 除外

Inside Derived::display() 里面的Derived :: display()

To understand this we need to understand v-table and vptr; 要理解这一点,我们需要了解v-table和vptr; when ever compiler find a virtual along with a function it will generate a virtual table for each of the classes (both Base and all the derived classes). 当编译器找到一个虚函数和一个函数时,它将为每个类(Base和所有派生类)生成一个虚拟表。

If virtual function is present than every object will be containing vptr (virtual pointer) pointing to the respective class vtable and vtable will contain the pointer to the respective class virtual function. 如果存在虚函数,则每个对象将包含指向相应类vtable的vptr(虚拟指针),而vtable将包含指向相应类虚函数的指针。 when you will call the function throught vptr the virutal function will get called and it will invoke the respective class function and we will achieve the required output. 当你通过vptr调用函数时,将调用virutal函数并调用相应的类函数,我们将实现所需的输出。

在此输入图像描述

Polymorphism and Dynamic Dispatch (hyper-abridged version) 多态性和动态调度(超删节版)

Note: I was not able to fit enough information about multiple inheritance with virtual bases, as there is not much of anything simple about it, and the details would clutter the exposition (further). 注意:我无法通过虚拟基础获得有关多重继承的足够信息,因为它没有太多简单的东西,并且细节会使展览混乱(进一步)。 This answer demonstrates the mechanisms used to implement dynamic dispatch assuming only single inheritance. 这个答案演示了用于实现动态调度的机制,假设只有单个继承。

Interpreting abstract types and their behaviors visible across module boundaries requires a common Application Binary Interface (ABI). 解释跨模块边界可见的抽象类型及其行为需要一个通用的应用程序二进制接口(ABI)。 The C++ standard, of course, does not require the implementation of any particular ABI. 当然,C ++标准不需要实现任何特定的ABI。

An ABI would describe: ABI会描述:

  • The layout of virtual method dispatch tables (vtables) 虚方法调度表(vtables)的布局
  • The metadata required for runtime type checks and cast operations 运行时类型检查和强制转换操作所需的元数据
  • Name decoration (aka mangling), calling conventions, and many other things. 名称装饰(aka mangling),调用约定和许多其他东西。

Both modules in the following example, external.so and main.o , are assumed to have been linked to the same runtime. 以下示例中的两个模块external.somain.o都假定已链接到同一运行时。 Static and dynamic binding give preference to symbols located within the calling module. 静态和动态绑定优先考虑位于调用模块内的符号。


An external library 外部图书馆

external.h (distributed to users): external.h(分发给用户):

class Base
{
    __vfptr_t __vfptr; // For exposition

public:

    __attribute__((dllimport)) virtual int Helpful();
    __attribute__((dllimport)) virtual ~Base();
};

class Derived : public Base
{
public:

    __attribute__((dllimport)) virtual int Helpful() override;

    ~Derived()
    {
        // Visible destructor logic here.


        // Note: This is in the header!


        // __vft@Base gets treated like any other imported symbol:
        // The address is resolved at load time.
        //
        this->__vfptr = &__vft@Base;
        static_cast<Base *>(this)->~Base();
    }
};

__attribute__((dllimport)) Derived *ReticulateSplines();

external.cpp: external.cpp:

#include "external.h" // the version in which the attributes are dllexport

__attribute__((dllexport)) int Base::Helpful()
{
    return 47;
}
__attribute__((dllexport)) Base::~Base()
{
}

__attribute__((dllexport)) int Derived::Helpful()
{
    return 4449;
}

__attribute__((dllexport)) Derived *ReticulateSplines()
{
    return new Derived(); // __vfptr = &__vft@Derived in external.so
}

external.so (not a real binary layout): external.so(不是真正的二进制布局):

__vft@Base:
[offset to __type_info@Base] <-- in external.so
[offset to Base::~Base] <------- in external.so
[offset to Base::Helpful] <----- in external.so

__vft@Derived:
[offset to __type_info@Derived] <-- in external.so
[offset to Derived::~Derived] <---- in external.so
[offset to Derived::Helpful] <----- in external.so

Etc...

__type_info@Base:
[null base offset field]
[offset to mangled name]

__type_info@Derived:
[offset to __type_info@Base]
[offset to mangled name]

Etc...

An application using the external library 使用外部库的应用程序

special.hpp: special.hpp:

#include <iostream>
#include "external.h"

class Special : public Base
{
public:

    int Helpful() override
    {
        return 55;
    }

    virtual void NotHelpful()
    {
        throw std::exception{"derp"};
    }
};

class MoreDerived : public Derived
{
public:

    int Helpful() override
    {
        return 21;
    }

    ~MoreDerived()
    {
        // Visible destructor logic here

        this->__vfptr = &__vft@Derived; // <- the version in main.o
        static_cast<Derived *>(this)->~Derived();
    }
};

class Related : public Base
{
public:

    virtual void AlsoHelpful() = 0;
};

class RelatedImpl : public Related
{
public:

    void AlsoHelpful() override
    {
        using namespace std;

        cout << "The time for action... Is now!" << endl;
    }
};

main.cpp: main.cpp中:

#include "special.hpp"

int main(int argc, char **argv)
{
    Base *ptr = new Base(); // ptr->__vfptr = &__vft@Base (in external.so)

    auto r = ptr->Helpful(); // calls "Base::Helpful" in external.so
    // r = 47

    delete ptr; // calls "Base::~Base" in external.so



    ptr = new Derived(); // ptr->__vfptr = &__vft@Derived (in main.o)

    r = ptr->Helpful(); // calls "Derived::Helpful" in external.so
    // r = 4449

    delete ptr; // calls "Derived::~Derived" in main.o



    ptr = ReticulateSplines(); // ptr->__vfptr = &__vft@Derived (in external.so)

    r = ptr->Helpful(); // calls "Derived::Helpful" in external.so
    // r = 4449

    delete ptr; // calls "Derived::~Derived" in external.so



    ptr = new Special(); // ptr->__vfptr = &__vft@Special (in main.o)

    r = ptr->Helpful(); // calls "Special::Helpful" in main.o
    // r = 55

    delete ptr; // calls "Base::~Base" in external.so



    ptr = new MoreDerived(); // ptr->__vfptr = & __vft@MoreDerived (in main.o)

    r = ptr->Helpful(); // calls "MoreDerived::Helpful" in main.o
    // r = 21

    delete ptr; // calls "MoreDerived::~MoreDerived" in main.o


    return 0;
}

main.o: main.o:

__vft@Derived:
[offset to __type_info@Derivd] <-- in main.o
[offset to Derived::~Derived] <--- in main.o
[offset to Derived::Helpful] <---- stub that jumps to import table

__vft@Special:
[offset to __type_info@Special] <-- in main.o
[offset to Base::~Base] <---------- stub that jumps to import table
[offset to Special::Helpful] <----- in main.o
[offset to Special::NotHelpful] <-- in main.o

__vft@MoreDerived:
[offset to __type_info@MoreDerived] <---- in main.o
[offset to MoreDerived::~MoreDerived] <-- in main.o
[offset to MoreDerived::Helpful] <------- in main.o

__vft@Related:
[offset to __type_info@Related] <------ in main.o
[offset to Base::~Base] <-------------- stub that jumps to import table
[offset to Base::Helpful] <------------ stub that jumps to import table
[offset to Related::AlsoHelpful] <----- stub that throws PV exception

__vft@RelatedImpl:
[offset to __type_info@RelatedImpl] <--- in main.o
[offset to Base::~Base] <--------------- stub that jumps to import table
[offset to Base::Helpful] <------------- stub that jumps to import table
[offset to RelatedImpl::AlsoHelpful] <-- in main.o

Etc...

__type_info@Base:
[null base offset field]
[offset to mangled name]

__type_info@Derived:
[offset to __type_info@Base]
[offset to mangled name]

__type_info@Special:
[offset to __type_info@Base]
[offset to mangled name]

__type_info@MoreDerived:
[offset to __type_info@Derived]
[offset to mangled name]

__type_info@Related:
[offset to __type_info@Base]
[offset to mangled name]

__type_info@RelatedImpl:
[offset to __type_info@Related]
[offset to mangled name]

Etc...

Invocation is (or might not be) Magic! 调用是(或可能不是)魔法!

Depending on the method and what can be proven at the binding side, a virtual method call may be bound statically or dynamically. 根据方法和绑定方面可以证明的内容,可以静态或动态地绑定虚方法调用。

A dynamic virtual method call will read the target function's address from the vtable pointed to by a __vfptr member. 动态虚方法调用将从__vfptr成员指向的vtable中读取目标函数的地址。

The ABI describes how functions are ordered in vtables. ABI描述了如何在vtable中排序函数。 For example: They might be ordered by class, then lexicographically by mangled name (which includes information about const-ness, parameters, etc...). 例如:它们可能按类排序,然后按字典顺序排列,包括错误的名称(包括有关常量,参数等的信息......)。 For single inheritance, this approach guarantees that a function's virtual dispatch index will always be the same, regardless of how many distinct implementations there are. 对于单继承,这种方法保证函数的虚拟调度索引始终是相同的,无论有多少不同的实现。

In the examples given here, destructors are placed at the beginning of each vtable, if applicable. 在此处给出的示例中,如果适用,将析构函数放置在每个vtable的开头。 If the destructor is trivial and non-virtual (not defined or does nothing), the compiler may elide it entirely, and not allocate a vtable entry for it. 如果析构函数是微不足道的和非虚拟的(未定义或什么也不做),编译器可能会完全忽略它,而不是为它分配vtable条目。

Base *ptr = new Special{};
MoreDerived *md_ptr = new MoreDerived{};

// The cast below is checked statically, which would
// be a problem if "ptr" weren't pointing to a Special.
//
Special *sptr = static_cast<Special *>(ptr);

// In this case, it is possible to
// prove that "ptr" could point only to
// a Special, binding statically.
//
ptr->Helpful();

// Due to the cast above, a compiler might not
// care to prove that the pointed-to type
// cannot be anything but a Special.
//
// The call below might proceed as follows:
//
// reg = sptr->__vptr[__index_of@Base::Helpful] = &Special::Helpful in main.o
//
// push sptr
// call reg
// pop
//
// This will indirectly call Special::Helpful.
//
sptr->Helpful();

// No cast required: LSP is satisfied.
ptr = md_ptr;

// Once again:
//
// reg = ptr->__vfptr[__index_of@Base::Helpful] = &MoreDerived::Helpful in main.o
//
// push ptr
// call reg
// pop
//
// This will indirectly call MoreDerived::Helpful
//
ptr->Helpful();

The logic above is the same for any invocation site that requires dynamic binding. 对于需要动态绑定的任何调用站点,上述逻辑都是相同的。 In the example above, it doesn't matter exactly what type ptr or sptr point to; 在上面的例子中, ptrsptr指向的确切类型sptr ; the code will just load a pointer at a known offset, then blindly call it. 代码只是将指针加载到已知的偏移量,然后盲目地调用它。


Type casting: Ups and Downs 类型铸造:上升和下降

All information about a type hierarchy must be available to the compiler when translating a cast or function call expression. 在转换强制转换或函数调用表达式时,编译器必须可以使用有关类型层次结构的所有信息。 Symbolically, casting is just a matter of traversing a directed graph. 象征性地,铸造只是遍历有向图的问题。

Up-casting in this simple ABI can be performed entirely at compile time. 这个简单的ABI中的向上转换可以在编译时完全执行。 The compiler needs only to examine the type hierarchy to determine if the source and target types are related (there is a path from the source to the target in the type graph). 编译器只需要检查类型层次结构以确定源类型和目标类型是否相关(在类型图中存在从源到目标的路径)。 By the substitution principle , a pointer to a MoreDerived also points to a Base and can be interpreted as such. 通过替换原则 ,指向MoreDerived的指针也指向Base并且可以解释为这样。 The __vfptr member is at the same offset for all types in this hierarchy, so RTTI logic doesn't need to handle any special cases (in certain implementations of VMI, it would need to grab another offset from a type thunk to grab another vptr and so on...). __vfptr成员对于此层次结构中的所有类型具有相同的偏移量,因此RTTI逻辑不需要处理任何特殊情况(在VMI的某些实现中,它需要从类型thunk中获取另一个偏移量以获取另一个vptr和等......)

Down-casting, however, is different. 然而,降级是不同的。 Since casting from a base type to a derived type involves determining if the pointed-to object has a compatible binary layout, it is necessary to perform an explicit type check (conceptually, this is "proving" that the extra information exists beyond the end of the structure assumed at compile time). 由于从基类型转换为派生类型涉及确定指向对象是否具有兼容的二进制布局,因此必须执行显式类型检查(从概念上讲,这是“证明”额外信息存在于结束之外在编译时假设的结构)。

Note that there are multiple vtable instances for the Derived type: One in external.so and one in main.o . 请注意, Derived类型有多个vtable实例:一个在external.so ,另一个在main.o This is because a virtual method defined for Derived (its destructor) appears in every translation unit that includes external.h . 这是因为为Derived (其析构函数)定义的虚方法出现在包含external.h每个翻译单元中。

Even though the logic is identical in both cases, both images in this example need to have their own copy. 尽管两种情况下的逻辑都相同,但本例中的两个图像都需要有自己的副本。 This is why type checking cannot be performed using addresses alone. 这就是为什么不能单独使用地址进行类型检查的原因。

A down-cast is then performed by walking a type graph (copied in both images) starting from the source type decoded at runtime, comparing mangled names until the compile-time target is matched. 然后通过从运行时解码的源类型开始遍历类型图(在两个图像中复制)来执行向下转换,比较错位名称直到编译时目标匹配。

For example: 例如:

Base *ptr = new MoreDerived();

// ptr->__vfptr = &__vft::MoreDerived in main.o
//
// This provides the code below with a starting point
// for dynamic cast graph traversals.

// All searches start with the type graph in the current image,
// then all other linked images, and so on...

// This example is not exhaustive!

// Starts by grabbing &__type_info@MoreDerived
// using the offset within __vft@MoreDerived resolved
// at load time.
//
// This is similar to a virtual method call: Just grab
// a pointer from a known offset within the table.
//
// Search path:
// __type_info@MoreDerived (match!)
//
auto *md_ptr = dynamic_cast<MoreDerived *>(ptr);

// Search path:
// __type_info@MoreDerived ->
// __type_info@Derived (match!)
//
auto *d_ptr = dynamic_cast<Derived *>(ptr);

// Search path:
// __type_info@MoreDerived ->
// __type_info@Derived ->
// __type_info@Base (no match)
//
// Did not find a path connecting RelatedImpl to MoreDerived.
//
// rptr will be nullptr
//
auto *rptr = dynamic_cast<RelatedImpl *>(ptr);

At no point in the code above did ptr->__vfptr need to change. 在上面的代码中没有任何一点需要改变ptr->__vfptr The static nature of type deduction in C++ requires the implementation to satisfy the substitution principle at compile time, meaning that the actual type of an object cannot change at runtime. C ++中类型推导的静态性质要求实现在编译时满足替换原则,这意味着对象的实际类型不能在运行时更改。


Summary 摘要

I've understood this question as one about the mechanisms behind dynamic dispatch. 我已经将这个问题理解为动态调度背后的机制。

To me, "Which entry in vtable refers to the function of "particular" derived classes which is supposed to be called at run time?" 对我来说, “vtable中的哪个条目指的是应该在运行时调用的”特定“派生类的功能?” , is asking how a vtable works. ,问的是vtable是如何工作的。

This answer is intended to demonstrate that type casting affects only the view of an object's data, and that the implementation of dynamic dispatch in these examples operate independently of it. 这个答案旨在证明类型转换只影响对象数据的视图,并且这些示例中的动态分派的实现独立于它运行。 However, type casting does affect dynamic dispatch in the case of multiple inheritance, where determining which vtable to use may require multiple steps (an instance of a type with multiple bases may have multiple vptrs). 但是,在多重继承的情况下,类型转换影响动态分派,其中确定要使用哪个 vtable可能需要多个步骤(具有多个基础的类型的实例可能具有多个vptrs)。

Let me try to explain it with some examples:- 让我试着用一些例子解释一下: -

class Base  
 {  
 public:  
    virtual void function1() {cout<<"Base :: function1()\n";};  
    virtual void function2() {cout<<"Base :: function2()\n";};  
    virtual ~Base(){};
};  

class D1: public Base  
{  
public:  
   ~D1(){};
   virtual void function1() { cout<<"D1 :: function1()\n";};
};  

class D2: public Base  
{  
public:  
   ~D2(){};
   virtual void function2() { cout<< "D2 :: function2\n";};  
}; 

So, compiler would generate three vtables one for each class as these classes have virtual functions. 因此,编译器将为每个类生成一个vtable,因为这些类具有虚函数。 ( Although it's compiler-dependant ). (虽然它依赖于编译器)。

NOTE:- vtables contain only pointers to virtual functions. 注意: - vtables仅包含指向虚函数的指针。 Non-virtual functions would still be resolved at compile time... 非虚函数仍然可以在编译时解决...

You are right in saying that vtables are nothing just pointers to functions. 你是对的,说vtable不仅仅是指向函数的指针。 vtables for these classes would be like something:- 这些类的vtables就像是: -

vtable for Base:- 基地vtable: -

&Base::function1 ();
&Base::function2 ();
&Base::~Base ();

vtable for D1:- D1的vtable: -

&D1::function1 ();
&Base::function2 ();
&D1::~D1();

vtable for D2:- D2的vtable: -

&Base::function1 ();
&D2::function2 ();
&D2::~D2 ();

vptr is a pointer which is used for look-up purpose on this table. vptr是一个指针,用于在此表上查找目的。 Each object of polymorphic class has extra allocated space for vptr in it ( Although where vptr would be in object is entirely implementation dependant ).Generally vptr is at the beginning of object. 多态类的每个对象都有额外的vptr空间(尽管vptr在对象中的位置完全取决于实现)。一般来说,vptr位于对象的开头。

With taking all into account , if I make a call to func, compiler at run time would check what b is actually pointing to:- 考虑到所有因素,如果我调用func,编译器在运行时将检查b实际指向的是什么: -

void func ( Base* b )
{
  b->function1 ();
  b->function2 ();
}

Let's say we have object of D1 passed to func. 假设我们将D1的对象传递给func。 Compiler would resolve calls in following manner:- 编译器将按以下方式解析呼叫: -

First it would fetch vptr from object and then it will use it to get correct address of function to call. 首先,它将从对象获取vptr,然后它将使用它来获取要调用的函数的正确地址。 SO, in this case vptr would give access to D1's vtable. 因此,在这种情况下,vptr将允许访问D1的vtable。 and when it looksup for function1 it would get the address of function1 defined in base class. 当它查找function1时,它将获得在基类中定义的function1的地址。 In case of call to function2, it would get address of base's function2. 在调用function2的情况下,它将获得base的function2的地址。

Hope I have clarified your doubts to your satisfaction... 希望我已经澄清了你的疑虑让你满意......

I believe, this is best explained by implementing polymorphism in C. Given these two C++ classes: 我相信,最好通过在C中实现多态来解释这个。给出这两个C ++类:

class Foo {
    virtual void foo(int);
};

class Bar : public Foo {
    virtual void foo(int);
    virtual void bar(double);
};

the C structure definitions (ie the header file) would look like this: C结构定义(即头文件)如下所示:

//For class Foo
typedef struct Foo_vtable {
    void (*foo)(int);
} Foo_vtable;

typedef struct Foo {
    Foo_vtable* vtable;
} Foo;

//For class Bar
typedef struct Bar_vtable {
    Foo_vtable super;
    void (*bar)(double);
}

typedef struct Bar {
    Foo super;
} Bar;

As you see, there are two structure definitions for each class, one for the vtable and one for the class itself. 如您所见,每个类有两个结构定义,一个用于vtable,另一个用于类本身。 Note also that both structures for class Bar include a base class object as their first member which allows us upcasting: both (Foo*)myBarPointer and (Foo_vtable*)myBar_vtablePointer are valid. 还要注意, class Bar两个结构都包含一个基类对象作为它们允许我们向上转换的第一个成员: (Foo*)myBarPointer(Foo_vtable*)myBar_vtablePointer都是有效的。 As such, given a Foo* , it is safe to find the location of the foo() member by doing 因此,给定一个Foo* ,通过这样做可以安全地找到foo()成员的位置

Foo* basePointer = ...;
(basePointer->vtable->foo)(7);

Now, lets take a look at how we can actually fill the vtables. 现在,让我们来看看我们如何实际填充vtable。 For that we write some constructors that use some statically defined vtable instances, this is what the foo.c file could look like 为此,我们编写了一些使用静态定义的vtable实例的构造函数,这就是foo.c文件的样子

#include "..."

static void foo(int) {
    printf("Foo::foo() called\n");
}

Foo_vtable vtable = {
    .foo = &foo,
};

void Foo_construct(Foo* me) {
    me->vtable = vtable;
}

This makes sure that it is possible to execute (basePointer->vtable->foo)(7) on every object that has been passed to Foo_construct() . 这确保了可以对已传递给Foo_construct()每个对象执行(basePointer->vtable->foo)(7) Foo_construct() Now, the code for Bar is quite similar: 现在, Bar的代码非常相似:

#include "..."

static void foo(int) {
    printf("Bar::foo() called\n");
}

static void bar(double) {
    printf("Bar::bar() called\n");
}

Bar_vtable vtable = {
    .super = {
        .foo = &foo
    },
    .bar = &bar
};

void Bar_construct(Bar* me) {
    Foo_construct(&me->super);    //construct the base class.
    (me->vtable->foo)(7);    //This will print Foo::foo()
    me->vtable = vtable;
    (me->vtable->foo)(7);    //This will print Bar::foo()
}

I have used static declarations for the member functions to avoid having to invent a new name for each implementation, static void foo(int) restricts the visibility of the function to the source file. 我已经为成员函数使用了静态声明,以避免为每个实现创建一个新名称, static void foo(int)限制了函数对源文件的可见性。 However, it can still be called from other files by the use of a function pointer. 但是,仍然可以通过使用函数指针从其他文件中调用它。

Usage of these classes could look like this: 这些类的用法可能如下所示:

#include "..."

int main() {
    //First construct two objects.
    Foo myFoo;
    Foo_construct(&myFoo);

    Bar myBar;
    Bar_construct(&myBar);

    //Now make some pointers.
    Foo* pointer1 = &myFoo, pointer2 = (Foo*)&myBar;
    Bar* pointer3 = &myBar;

    //And the calls:
    (pointer1->vtable->foo)(7);    //prints Foo::foo()
    (pointer2->vtable->foo)(7);    //prints Bar::foo()
    (pointer3->vtable->foo)(7);    //prints Bar::foo()
    (pointer3->vtable->bar)(7.0);  //prints Bar::bar()
}

Once you know how this works, you know how C++ vtables work. 一旦你知道它是如何工作的,你就知道C ++ vtable是如何工作的。 The only difference is that in C++ the compiler does the work that I did myself in the code above. 唯一的区别是在C ++中编译器完成了我在上面的代码中完成的工作。

The implementation is compiler specific. 该实现是特定于编译器的。 Here I am going to do some thoughts that have NOTHING TO DO WITH ANY ACTUAL KNOWLEDGE of how exactly it is done in compilers, but just with some minimal requirements that are needed in order to work as required. 在这里,我将做一些关于在编译器中如何完成它的任何实际知识无关的想法,但只是为了按要求工作所需的一些最低要求。 Keep in mind that each instance of a class with virtual methods knows at run time which is the class it belongs too. 请记住,具有虚方法的类的每个实例在运行时都知道它所属的类。

Lets suppose we have a chain of base and derived classes with a length of 10 ( so a derived class has a gran gran ... gran father ). 让我们假设我们有一个长度为10的基类和派生类的链(因此派生类有一个gran gran ... gran父亲)。 We may call these classes base0 base1 ... base9 where base9 derives from base8 etc. 我们可以将这些类称为base0 base1 ... base9,其中base9派生自base8等。

Each of these classes define a method as: virtual void doit(){ ... } 这些类中的每一个都将方法定义为:virtual void doit(){...}

Let's suppose that in the base class we use that method inside a method called "dowith_doit" non overridden in any derived class. 让我们假设在基类中,我们在任何派生类中未被覆盖的名为“dowith_doit”的方法中使用该方法。 The semantics of c++ imply that depending on the base class of the instance we have at hand, we must apply to that instance the "doit" defined in the base class of the instance at hand. c ++的语义意味着,根据我们手头的实例的基类,我们必须在该实例中应用手头实例的基类中定义的“doit”。

Essentially we have two possible ways of doing it: a) Assign to any such virtual method a number that must be different for each method defined in the chain of derived classes. 基本上我们有两种可能的方法:a)为任何这样的虚方法分配一个数字,该数字对于派生类链中定义的每个方法必须是不同的。 In that case the number could be also a hash of the name of the method. 在这种情况下,该数字也可以是方法名称的散列。 Each class defines a table with 2 columns were the first column holds the number of the method and the second column the address of the function. 每个类定义一个包含2列的表,第一列包含方法的编号,第二列包含函数的地址。 In that case each class will have a vtable with so many rows as the number of virtual methods defined inside the class. 在这种情况下,每个类都有一个vtable,其行数与在类中定义的虚拟方法的数量相同。 The execution of the method happens by searching inside the class the method under consideration. 通过在类中搜索所考虑的方法来执行该方法。 That search may be done linearly ( slow ) of by bisections ( when there is an order based on the number of the method). 该搜索可以通过二分(当存在基于方法的数量的顺序时)线性地(缓慢地)完成。

b) Assign to any such method a progressively increasing integer number (for each different method in the chain of classes), and for each class define a table with only one column. b)为任何此类方法分配逐步增加的整数(对于类链中的每个不同方法),并为每个类定义一个只有一列的表。 For virtual methods defined inside the class the function address will be in the raw defined by the number of the method. 对于在类中定义的虚方法,函数地址将是由方法编号定义的raw。 There will be many rows with null pointers because each class doesn't override always the methods of previous classes. 将有许多带有空指针的行,因为每个类都不会覆盖以前类的方法。 The implementation may choose in order to improve efficiency to fill null rows with the address hold in the ancestor class of the class under consideration. 实现可以选择以提高效率以使用所考虑的类的祖先类中的地址保持来填充空行。

Essentially no other simple ways exist in order work with virtual methods efficiently. 基本上没有其他简单方法可以有效地使用虚拟方法。

I suppose that only the second solution (b) is used in actual implementations, because the trade of between space overhead used for non existing methods compared to execution efficiency of case (b) is favorable for case b (taking into consideration too that methods are limited in number - may be 10 20 50 but not 5000 ). 我认为在实际实现中只使用第二个解决方案(b),因为用于非现有方法的空间开销与情况(b)的执行效率之间的交易对于情况b是有利的(同时考虑到方法是数量有限 - 可能是10 20 50但不是5000)。

Upon instantiation every class with at least one virtual function gets a hidden member usually called vTable (or virtual dispatch table, VDT). 在实例化时,具有至少一个虚函数的每个类获得通常称为vTable(或虚拟调度表,VDT)的隐藏成员。

class Base {
hidden: // not part of the language, just to illustrate.
  static VDT baseVDT; // per class VDT for base
  VDT *vTable; // per object instance
private:
  ...
public:
  virtual int base1();
  virtual int base2();
  ...
};

The vTable contains pointers to all functions in Base. vTable包含指向Base中所有函数的指针。

As a hidden part of Base's constructor vTable gets assigned to baseVDT. 作为Base的构造函数的隐藏部分,vTable被分配给baseVDT。

VDT Base::baseVDT[] = { 
  Base::base1, 
  Base::base2 
};

class Derived : public Base {
hidden:
  static VDT derivedVDT; // per class VDT for derived
private:
  ...
public:
  virtual int base2();
  ...
};

The vTable for Derived contains pointers to all functions defined in Base followed by functions defined in Derived . 用于Derived的vTable包含指向Base中定义的所有函数的指针,后跟Derived中定义的函数。 When objects of type Derived gets constructed, vTable gets set to derivedVDT. 构造Derived类型的对象时,vTable将设置为derivedVDT。

VDT derived::derivedVDT[] = { 
  // functions first defined in Base
  Base::base1, 
  Derived::base2, // override
  // functions first defined in Derived are appended
  Derived::derived3 
}; // function 2 has an override in derived.

Now if we have 现在,如果我们有

Base *bd    = new Derived;
Derived *dd = new Derived;
Base *bb    = new Base;

bd points to an object of type derived who's vTable points to Derived bd指向派生类型的对象,其vTable指向Derived

So the function calls 所以函数调用

x = bd->base2();
y = bb->base2();

actually is 实际上是

// "base2" here is the index into vTable for base2.
x = bd->vTable["base2"]();  // vTable points to derivedVDT
y = bb->vTable["base2"]();  // vTable points to baseVDT

The index is the same in both due to the construction of the VDT. 由于VDT的构建,两者的指数相同。 This also means the compiler knows the index at the moment of compilation. 这也意味着编译器在编译时知道索引。

This could also be implemented as 这也可以实现为

// call absolute address to virtual dispatch function which calls the right base2.
x = Base::base2Dispatch(bd->vTable["base2"]); 

inline Base::base2Dispatch(void *call) {
  return call(); // call through function pointer.
}

Which with O2 or O3 will be the same. 与O2或O3相同。


There are some special cases: 有一些特殊情况:

dd points to a derived or more derived object and base2 is declared final then dd指向派生的或更多派生的对象,然后base2被声明为final

z = dd->base2();

actually is 实际上是

z = Derived::base2();  // absolute call to final method.

If dd pointed to a Base object or anything else your in undefined behaviour land and the compiler can still do this. 如果dd指向Base对象或其他任何未定义的行为,编译器仍然可以执行此操作。

The other case is if the compiler sees there are only a few derived classes from Base it could generate a Oracle interface for base2. 另一种情况是,如果编译器发现只有少数来自Base的派生类,它可以为base2生成Oracle接口。 [free after a MS or Intel compiler guy at some C++ conference in 2012 or 2013? [在2012年或2013年的某个C ++大会上,MS或英特尔编译人员可以免费使用? showing that (~500%?) more code gives (2+ times?) speedup on average] 显示(~500%?)更多代码平均加速(2倍以上?)加速

inline Base::base2Dispatch(void *call) {
  if (call == Derived::base2)  // most likely from compilers static analysis or profiling.
    return Derived::base2(); // call absolute address
  if (call == Base::base2)
    return Base::base2(); // call absolute address

  //  Backup catch all solution in case of more derived classes
  return call(); // call through function pointer.
}

Why on earth do you want to do this as a compiler??? 你为什么要这样做呢? more code is bad, unneeded branches diminish performance! 更多的代码是坏的,不需要的分支会降低性能!

Because calling a function pointer is very slow on many architectures, optimistic example 因为在许多体系结构上调用函数指针非常慢,乐观的例子

Get the address from memory, 3+ cycles. 从内存中获取地址,3个周期以上。 Delayed pipeline while waiting for ip value, 10 cycles, on some processors 19+ cycles. 在某些处理器19+周期等待ip值,10个周期的同时延迟流水线。

If the most complex modern cpu's can predict the actual jump address [BTB] as well as it does branch prediction, this might be a loss. 如果最复杂的现代cpu可以预测实际的跳转地址[BTB]以及它进行分支预测,那么这可能是一种损失。 Else the ~8 extra instructions will easily save the 4*(3+10) instructions lost due to pipeline stalls (if the prediction failure rate is less than 10-20%). 否则,~8个额外指令将轻松保存由于流水线停滞而丢失的4 *(3 + 10)指令(如果预测失败率小于10-20%)。

If the branches in the two if's both predict taken (ie evaluate to false) the ~2 cycles lost is nicely covered by the memory latency to get the call address and we are no worse off. 如果两个if中的分支都被预测(即评估为假),则丢失的~2个周期很好地被内存延迟覆盖以获得呼叫地址,并且我们并没有更糟糕。
If one of the if's are mispredicts the the BTB will most likely also be wrong. 如果其中一个if是错误预测,那么BTB很可能也是错误的。 Then the cost of the mispredicts is around 8 cycles of which 3 are paid by the memory latency, and the correct not take or the 2nd if might save the day or we pay the full 10+ pipeline stall. 那么错误预测的成本大约是8个周期,其中3个由内存延迟支付,正确的不占用或第2个如果可以节省一天或我们支付完整的10+管道停顿。
If only the 2 possibilities exists one of them will be taken and we save the pipeline stall from the function pointer call and we will max. 如果仅存在两种可能性, 则将采用其中一种,并且我们将保存来自函数指针调用的管道停顿,并且我们将最大。 get one mispredict resulting in no (significant) worse performance than calling directly. 得到一个误预测导致没有(显着)比直接调用更差的性能。 If the memory delay is longer and the result is correctly predicted the effect is much larger. 如果内存延迟较长并且结果得到正确预测,则效果会更大。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM