vtables 在 c++ 中是如何实现的?和c#?

发布于 2024-09-17 17:31:24 字数 465 浏览 8 评论 0原文

让我们遇到这种情况(在 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?投射时是否会进行任何指针修复?

Lets have this situation (in c++, in c# classes A,B are interfaces):

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?

Does the c# compiler create always one vtable? Does it make any pointer fixups when casting?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

说不完的你爱 2024-09-24 17:31:24

不想过于迂腐,但 C# 编译器不会参与此级别。整个类型模型、继承、接口实现等实际上由 CLR 处理,更具体地说是 CTS(通用类型系统)。 .NET 编译器大多只是生成表示意图的 IL 代码,稍后由 CLR 执行,其中所有 Vtable 处理等均由 CLR 负责。

有关 CLR 如何创建和管理运行时类型的一些详细信息,以下链接将是一个很好的起点。最后解释了方法表和接口映射。

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

Not to be overly pedantic, but the C# compiler does not get involved at this level. The Entire type model, inheritance, interface implementation etc. is actually handled by the CLR more specifically the CTS (Common Type System). .NET compilers mostly just generate IL code that represents intent which is later executed by the CLR where all Vtable handling etc. is taken care of.

For some detail on how the CLR creates and manages runtime types the following link will be a good starting point. Towards the end the MethodTable and Interface Maps are explained.

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

饮湿 2024-09-24 17:31:24

如果我用 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    

If I study this derived version with 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]);

}

It turns out that, in C++ b == a+1, so the structure for X is [vtable-X+A][vtable-B][magic][var]
inspecting deeper (nm ./a.out), vtable-X+a contains the reference towards X::func (as one would expect). when you casted your X into B, it adjusted the pointers so that the VTBL for B functions appears where the code expects it.

Did you actually intend to "hide" B::func() ?

B's vtbl looks like holding a reference towards a "trampoline" to X that restores the object pointer to a full X before calling the "regular" X::func that X+A vtbl holds.

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    
仅此而已 2024-09-24 17:31:24

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

当尝试声明一个本身是从 IUnknown 之外的另一个接口声明的 COM 接口时,这是一个值得注意的问题。 本文作者不太理解这个问题。 COM 需要为每个接口提供一个单独的 v-table,这正是支持 MI 的编译器所做的。

Yes, there is only ever one v-table in a managed language, the CLR does not support multiple inheritance. There is a pointer fixup when you cast to an implemented interface.

This is a notable problem when trying to declare a COM interface that is itself declared from another interface beyond IUnknown. An issue not quite understood by this article's author. COM requires a separate v-table for each interface, just what a compiler that supports MI does.

就是爱搞怪 2024-09-24 17:31:24

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

vtables are an implementation detail. There is no official/required/expected implementation. Different compiler vendors can implement inheritance differently.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文