【问题标题】:How are vtables implemented in c++ and c#?c++ 和 c# 中的 vtables 是如何实现的?
【发布时间】:2011-04-07 18:32:54
【问题描述】:

让我们有这种情况(在 c++ 中,在 c# 中类 A、B 是接口):

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# 编译器是否总是创建一个 vtable?投射时是否会修正指针?

【问题讨论】:

  • 我想你的意思是a == xb != x。由于指针在堆栈上一个接一个地分配,它们都有一个不同的地址......尽管它们都应该指向同一个对象。
  • 当然是我的错 :) 现在修好了,我忘了 a == b 比较地址

标签: c# c++ inheritance interface vtable


【解决方案1】:

如果我用 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 == a+1,所以 X 的结构是 [vtable-X+A][vtable-B][magic][var] 更深入地检查(nm ./a.out),vtable-X+a 包含对 X::func 的引用(正如人们所期望的那样)。当您将 X 转换为 B 时,它会调整指针,以便 B 函数的 VTBL 出现在代码期望的位置。

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

B 的 vtbl 看起来像持有对 X 的“蹦床”的引用,它在调用 X+A vtbl 持有的“常规”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    

【讨论】:

  • 你真的只是extern "C" main 吗?
  • 嗯...不知道。看起来这不是我通常在其他 C++ 工具中做的事情。这只会影响名称修改和外部可见性,对吧?
  • 这里很有趣))
【解决方案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

【讨论】:

  • 这个链接现在坏了——它只是指向 MSDN 杂志过刊的索引。要查看原始文章,请下载 2005 年 5 月的 .CHM 文件,取消阻止它,然后转到文章“JIT 和运行:深入了解 .NET Framework 内部结构以了解 CLR 如何创建运行时对象”。 (或者,谷歌标题。)我会在链接中编辑,但似乎没有“官方”链接。
  • 谢谢,我用回溯时光机的原文链接替换了链接。
【解决方案3】:

是的,托管语言中只有一个 v-table,CLR 不支持多重继承。当您转换为已实现的接口时,会有一个指针修复。

当尝试声明一个 COM 接口时,这是一个值得注意的问题,该接口本身是从 IUnknown 之外的另一个接口声明的。 this article's作者不太了解的问题。 COM 要求每个接口都有一个单独的 v-table,这正是支持 MI 的编译器所做的。

【讨论】:

  • 如果只有一个vtable,c#如何支持菱形接口继承?
  • @Chris,如果您查看我在回答中提供的链接,您应该对它的工作原理有一个基本的了解。这里有一句名言“插槽的重复是必要的,以创建每个接口都有自己的迷你 vtable 的错觉。但是,重复的插槽指向相同的物理实现。”
  • 钻石会引起麻烦,因为它变得不明确使用哪个实现。接口没有实现。只能有一个基类。
【解决方案4】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-05
    • 2018-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-12
    • 2011-07-18
    相关资源
    最近更新 更多