2010-09-03 111 views
5

让有这种情况(在C++,C#中类A,B是接口):vtable如何在C++和c#中实现?

class A { virtual void func() = 0; }; 
class B { virtual void func() = 0; }; 
class X: public A, public B { virtual void func(){ var = 1; } int var;}; 

X * x = new X; // from what I know, x have 2 vtables, is this the same in c#? 
A * a = (A*)x; // a == x 
B * b = (B*)x; // here b != x, so when calling b->func(), how is the address of var correct? 

请问C#编译器总是创建一个虚函数表?投射时是否会进行指针修正?

+2

我想你的意思是'a == x'和'b!= x'。由于指针在堆栈中被一个接一个地分配,所以它们都有一个独特的地址......尽管它们都应该指向同一个对象。 – PypeBros 2010-09-03 10:05:06

+0

当然,我的错误:)现在已经修复了,我忘记了==b比较地址 – chris 2010-09-03 10:07:29

回答

2

不要过于迂腐,但C#编译器不会涉及这个级别。整体类型模型,继承,接口实现等实际上由CLR更具体地由CTS(通用类型系统)处理。 .NET编译器通常只生成表示意图的IL代码,稍后由CLR执行所有Vtable处理等。

有关CLR如何创建和管理运行时类型的详细信息,以下链接将是一个很好的起点。最后解释了MethodTable和Interface Maps。

http://web.archive.org/web/20150515023057/https://msdn.microsoft.com/en-us/magazine/cc163791.aspx

+0

现在这个链接被破坏 - 它只是去了MSDN杂志的后期问题索引。要查看原文,请下载2005年5月的.CHM文件,取消阻止它,然后转到文章“JIT并运行:钻入.NET Framework内部以查看CLR如何创建运行时对象”。 (另外,谷歌标题。)我会编辑一个链接,但似乎并没有一个“官方”。 – 2017-02-07 16:25:23

+0

谢谢,我通过wayback时间机器将链接替换为原始文章的链接。 – 2017-02-08 05:07:25

2

是的,只有永远一个v表的管理语言,在CLR不支持多重继承。当你投射到一个实现的界面时有一个指针修正。

这是一个值得注意的问题,当试图声明一个COM接口本身是从另一个接口声明超出IUnknown。 this article's作者不完全明白的问题。对于每个接口,COM需要一个单独的 v-表,正是支持MI的编译器所做的。

+0

如果只有一个vtable,c#如何支持菱形接口继承? – chris 2010-09-03 10:21:29

+1

@Chris,如果你看看我在答案中提供的链接,你应该了解它的工作原理。这里有一句话:“插槽的重复是必要的,以产生一个错觉,即每个接口都有自己的微型虚拟表,但是重复的插槽指向相同的物理实现。” – 2010-09-03 10:29:27

+2

钻石造成麻烦,因为它使用*执行*变得模糊。一个接口没有实现。只能有一个基类。 – 2010-09-03 10:29:41

3

如果我研究使用g ++

class X: public A, public B { 
    unsigned magic; 
public: 
    X() : magic(0xcafebabe) {}; 
    virtual void func(){ var = 1; } int var; 
}; 

extern "C" int main() 
{ 
    X * x = new X; // from what I know, x have 2 vtables, is this the same in c#? 
    A * a = (A*)x; // &a == &x 
    B * b = (B*)x; // here &b != &x, so when calling b->func(), how is the address of var correct? 
    printf("%p -- %p -- %p\n", x, a, b); 

    unsigned* p = (unsigned*)((void*) x); 
    unsigned *q = (unsigned*)(p[1]); 
    printf("x=[%x %x %x %x]\n",p[0],p[1],p[2],p[3]); 
    p = (unsigned*)(p[0]); 
    printf("a=[%x %x %x %x]\n",p[0],p[1],p[2],p[3]); 
    printf("b=[%x %x %x %x]\n",q[0],q[1],q[2],q[3]); 

} 

此派生版本事实证明,在C++ b ==一个+ 1,因此X的结构为[虚表-X + A] [虚表-B ] [magic] [var] 检查更深(nm ./a.out),vtable-X + a包含对X :: func的引用(正如人们所期望的)。当你将X转换成B时,它调整了指针,以便B代码的VTBL出现在代码期望的地方。

你真的打算“隐藏”B :: func()吗?

B的vtbl看起来像持有对X的“蹦床”的引用,它在调用X + A vtbl拥有的“regular”X :: func之前将对象指针恢复为全X。

080487ea <_ZThn8_N1X4funcEv>: # in "X-B vtbl" 
_ZThn8_N1X4funcEv(): 
80487ea:  83 44 24 04 f8   addl $0xfffffff8,0x4(%esp) 
80487ef:  eb 01     jmp 80487f2 <_ZN1X4funcEv> 
80487f1:  90      nop 

080487f2 <_ZN1X4funcEv>:  # in X-A vtbl 
_ZN1X4funcEv(): 
80487f2:  55      push %ebp 
80487f3:  89 e5     mov %esp,%ebp 
80487f5:  8b 45 08    mov 0x8(%ebp),%eax 
80487f8:  c7 40 14 01 00 00 00 movl $0x1,0x14(%eax) 
80487ff:  5d      pop %ebp 
8048800:  c3      ret  
+2

你真的只是'extern“C”'main? – 2013-01-05 15:34:02

+2

嗯......不知道。看起来这不是我通常在其他C++工具中做的事情。这只会影响名称修改和外部可见性,对吗? – PypeBros 2013-01-05 22:06:18

+0

这里很有趣)) – rostamn739 2016-06-12 14:42:19

1

vtables是一个实现细节。没有官方/必需/预期的实施。不同的编译器供应商可以不同地实现继承