2010-09-03 14 views
5

Permet d'avoir cette situation (en C++, c dans les classes de # A, B sont des interfaces):Comment les vtables sont-ils implémentés en C++ et 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? 

Est-ce que le compilateur C# créer toujours un vtable? Fait-il des corrections de pointeur lors du casting?

+2

Je suppose que vous voulez dire 'a == x' et' b! = X'. Puisque les pointeurs sont alloués les uns après les autres sur la pile, ils ont tous une adresse distincte ... bien qu'ils devraient tous pointer vers le même objet. – PypeBros

+0

bien sûr, mon erreur :) Il est maintenant fixé, j'ai oublié que a == b compare les adresses – chris

Répondre

2

Ne pas être trop pédant, mais le compilateur C# n'intervient pas à ce niveau. Le modèle de type entier, l'héritage, la mise en œuvre de l'interface, etc. est en fait traité par le CLR, plus précisément le CTS (Common Type System). Les compilateurs .NET génèrent pour la plupart juste du code IL qui représente l'intention qui est ensuite exécutée par le CLR où toute la gestion de Vtable est prise en charge.

Pour plus de détails sur la façon dont le CLR crée et gère les types d'exécution, le lien suivant sera un bon point de départ. Vers la fin, les tables MethodTable et Interface sont expliquées.

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

+0

Ce lien est cassé maintenant - il va juste à un index des anciens numéros du magazine MSDN. Pour voir l'article original, téléchargez le fichier .CHM pour mai 2005, débloquez-le, puis accédez à l'article "JIT and Run: Exploration des composants internes .NET Framework pour voir comment le CLR crée des objets d'exécution". (Alternativement, google le titre.) Je éditerais dans un lien mais il ne semble pas être un "officiel". –

+0

Merci, j'ai remplacé le lien avec un lien vers l'article original via la machine à remonter le temps. –

2

Oui, il n'y a jamais un vtable dans une langue gérée, le CLR ne prend pas en charge l'héritage multiple. Il y a une correction de pointeur lorsque vous lancez vers une interface implémentée.

Ceci est un problème notable lorsque vous essayez de déclarer une interface COM qui est elle-même déclarée à partir d'une autre interface au-delà de IUnknown. Un problème pas tout à fait compris par l'auteur this article's. COM nécessite une table v distincte pour chaque interface, ce que fait un compilateur qui prend en charge MI.

+0

S'il n'y a qu'une seule vtable, comment C# supporte l'héritage de l'interface diamant? – chris

+1

@Chris, si vous jetez un oeil sur le lien que j'ai fourni dans ma réponse, vous devriez avoir une idée de base de la façon dont cela fonctionne.Voici une citation "La duplication des slots est nécessaire pour créer l'illusion que chaque interface a son propre mini vtable Cependant, les slots dupliqués pointent vers la même implémentation physique." –

+2

Le diamant provoque des problèmes car il devient ambigu quelle * mise en œuvre * à utiliser. Une interface n'a pas d'implémentation. Il ne peut y avoir qu'une seule classe de base. –

3

Si j'étudie cette version dérivée de 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]); 

} 

Il se trouve que, dans de C de b == a + 1, de sorte que la structure de X est [vtable-X + A] [vtable-B ] [magic] [var] En examinant plus profond (nm ./a.out), vtable-X + a contient la référence vers X :: func (comme on pourrait s'y attendre). Lorsque vous avez casté votre X dans B, il a ajusté les pointeurs de sorte que les fonctions VTBL pour B apparaissent là où le code l'attend.

Aviez-vous l'intention de "cacher" B :: func()?

Le vtbl de B ressemble à une référence vers un "trampoline" à X qui restitue le pointeur d'objet à un X complet avant d'appeler la fonction X "régulière" que X + A vtbl contient.

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

Avez-vous vraiment juste 'extern 'C" 'main? –

+2

Hmm ... ne sais pas. On dirait que ce n'est pas quelque chose que je fais habituellement dans mes autres outils C++. Cela n'affecte que le dénigrement des noms et la visibilité externe, n'est-ce pas? – PypeBros

+0

Tellement amusant ici)) – rostamn739

1

vtables sont un détail d'implémentation. Il n'y a pas de mise en œuvre officielle/requise/prévue. Différents fournisseurs de compilateurs peuvent implémenter l'héritage différemment.