首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >编译器如何为虚拟函数调用生成代码?

编译器如何为虚拟函数调用生成代码?
EN

Stack Overflow用户
提问于 2014-02-04 20:48:28
回答 4查看 2.4K关注 0票数 19

代码语言:javascript
运行
复制
CAT *p;
...
p->speak();
...

有些书说编译器会将p-> that ()翻译成:

代码语言:javascript
运行
复制
(*p->vptr[i])(p); //i is the idx of speak in the vtbl

我的问题是:因为在编译时,不可能知道p的真正类型,这意味着不可能知道要使用哪个vptr或vtbl。那么,编译器如何生成正确的代码呢?

改性

例如:

代码语言:javascript
运行
复制
void foo(CAT* c)
{
    c->speak();
    //if c point to SmallCat
    // should translate to (*c->vptr[i])(p); //use vtbl at 0x1234   
    //if c point to CAT
    // should translate to (*c->vptr[i])(p); //use vtbl at 0x5678  

    //since ps,pc all are CAT*, why does compiler can generate different code for them 
    //in compiler time?
}

...
CAT *ps,*pc;
ps = new SmallCat;  //suppose SmallCat's vtbl address is 0x1234;
pc = new CAT;       //suppose CAT's vtbl address is 0x5678;
...
foo(ps);
foo(pc)
...

有什么想法吗?谢谢。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2014-02-04 20:53:27

您所缺少的是来自CATSmallCAT对象的箭头,指向它们相应的vtbls。编译器将指向vtbl的指针嵌入到对象本身--可以将其视为隐藏的成员变量。这就是为什么在内存占用中,添加第一个虚拟函数“花费”每个对象一个指针的原因。指向vtbl的指针是由构造函数中的代码设置的,因此编译器生成的虚拟调用要在运行时到达其vtable,所需做的就是取消对指向this的指针的引用。

当然,在虚拟继承和多重继承中,这会变得更加复杂:编译器需要生成稍微不同的代码,但是基本过程保持不变。

下面是详细说明的示例:

代码语言:javascript
运行
复制
CAT *p1,*p2;
p1 = new SmallCat;  //suppose its vtbl address is 0x1234;
// The layout of SmallCat object includes a vptr as a hidden member.
// At this point, the value of this vptr is set to 0x1234.
p2 = new CAT;       //suppose its vtbl address is 0x5678;
// The layout of Cat object also includes a vptr as a hidden member.
// At this point, the value of this vptr is set to 0x5678.
(*p1->vptr[i])(p); //should use vtbl at 0x1234
// Compiler has enough information to do that, because it squirreled away 0x1234
// inside the SmallCat object at the time it was constructed.
(*p2->vptr[i])(p); //should use vtbl at 0x5678
// Same deal - the constructor saved 0x5678 inside the Cat, so we're good.
票数 20
EN

Stack Overflow用户

发布于 2014-02-04 20:51:57

这意味着不可能知道要使用哪个vptr或vtbl。

在方法调用期间,这是正确的。但是在构造时,构造的对象的类型实际上是已知的,编译器将在ctor中生成代码来初始化vptr以指向相应类的vtbl。所有后面的虚拟方法调用都将通过这个vptr调用右vtbl中的方法。

有关此初始化如何使用基本对象(多个ctors按顺序调用)的更多详细信息,请参考this answer中类似的问题。

票数 9
EN

Stack Overflow用户

发布于 2014-02-04 21:02:09

编译器隐式地向每个具有一个或多个虚拟函数的类添加一个名为vptr的指针。

您可以通过在这类上使用sizeof来判断这一点,并看到它比预期的要大4或8个字节,这取决于sizeof(void*)

编译器还添加到每个类的构造函数中,这是一个隐式代码,它将vptr设置为指向一个函数指针表(a.k.a )。(五-桌)

当一个对象被实例化时,它的类型被显式地“提到”。

例如:A a(1)A* p = new B(2)

因此,在构造函数运行时中,可以很容易地将vptr设置为指向正确的V-表。

在上面的例子中:

  • vptr of a设置为指向class A的V表.
  • vptr of p设置为指向class B的V表.

顺便说一句,构造函数不同于所有其他函数,因为您必须显式地使用对象类型来调用它(因此,构造函数永远不能声明为虚拟的)。

这里是编译器如何为虚拟函数p->speak()**:**生成正确的代码

代码语言:javascript
运行
复制
CAT *p;
...
p = new SuperCat("SaberTooth",2); // p->vptr = SuperCat_Vtable
...
p->speak(); // See pseudo assembly code below

Ax = p               // Get the address of the instance
Bx = p->vptr         // Get the address of the instance's V-Table
Cx = Bx + CAT::speak // Add the number of the function in its class
Dx = *Cx             // Get the address of the appropriate function
Push Ax              // Push the address of the instance into the stack
Push Dx              // Push the address of the function into the stack
CallF                // Save some registers and jump to the beginning of the function

编译器对speak层次结构中的所有class CAT函数使用相同的数字(索引)。

这里是编译器如何为非虚拟函数p->eat()**:**生成正确的代码。

代码语言:javascript
运行
复制
p->eat(); // See pseudo assembly code below

Ax = p        // Get the address of the instance
Bx = CAT::eat // Get the address of the function
Push Ax       // Push the address of the instance into the stack
Push Bx       // Push the address of the function into the stack
CallF         // Save some registers and jump to the beginning of the function

由于eat函数的地址是在编译时已知的,所以程序集代码效率更高.

,最后,下面是如何将“vptr”设置为在运行时指向正确的V-表:

代码语言:javascript
运行
复制
class SmallCat
{
    void* vptr; // implicitly added by the compiler
    ...         // your explicit variables
    SmallCat()
    {
        vptr = (void*)0x1234; // implicitly added by the compiler
        ...                   // Your explicit code
    }
};

实例化CAT* p = new SmallCat()时,将创建一个新对象及其vptr = 0x1234

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/21563065

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档