我正在从事有关编译器设计的任务。在代码生成部分中,我坚持如何创建指令以确保在运行时调用适当的方法。该语言是C ++的很小一部分。

比方说:

void main()
{
  Animal* a;
  a = new Cow;
  //what code should be generated to ensure that object 'a' calls Cow::Init here
  a->Init(5);
}

class Cow : public Animal{
 void Init(int h)
 {
   height = h;
 }
}

class Animal {
 int height;
 virtual void Init(int h){
   height = h;
  }
}

最佳答案

一种非常简单的方法(注意:这不包括针对编译时的已知调用进行优化):
如果您的类具有任何虚拟成员(包括继承的成员),则其第一个成员将成为指向vftable的指针。 vftable在每个类定义中都是常量,这就是为什么只需要一个指针的原因。

从那里开始,每个唯一函数都在该vftable中分配了一个索引,因此每个唯一名称(请注意:按名称,我的意思是符号名称包括类型,但没有类命名空间限定)具有唯一索引,然后从继承树最顶部的类,直到您当前的工作类定义。

这样,较新的虚拟函数重新定义将覆盖共享其索引的较旧条目。然后,只需为该函数的name-index生成对索引的调用,调用函数就变得不那么重要了。

因此,在您的示例中,Animal的vftable有1个条目,Init(int),其唯一索引为0。因此,您有一个vftable,如下所示:

;Animal - vftable
&Animal::Init //note: this isn't a class member pointer in the C++ sense, its a namespaced function pointer if you will


然后,当为Cow构建vftable时,将Animals用作基础并添加虚拟函数(在本例中为Init(int)),但它已经具有唯一索引0,因此我们在索引处覆盖该函数。 0:

;Cow - vftable
&Cow::Init


那么如果我们有电话:

a->Init(5);


我们只是将其转换为:

a->vftable[0](5);


其中0是分配给Init(int)的唯一索引。

一个组装示例,以防万一:

;ecx contains our class pointer
mov eax,[ecx] ;get the vftable ptr
mov eax,[eax] ; get the ptr at (vftable + (unique_index * sizeof(func_ptr)))
push 5 ;push our arg 5, ecx is already setup for __thiscall
call eax ; let it rip!


注意:这全部假设您的符号表已设置为能够检测通过继承传递的虚拟函数或从继承变为虚拟的函数。



如果要对此进行优化,则可以分析a并发现它仅分配了一次值,因此您可以将其类变形为为其分配的值的类,即Cow。然后看到派生链末尾有一个类,您可以折叠vftable调用并直接使用Cow::Init调用,这要棘手得多,并且有许多方法可以优化vftable调用,对于一个项目来说没关系。

09-30 12:27