Wie eine andere Person sagte, schreibt der C++ Standard keine virtuelle Methodentabelle vor, sondern erlaubt die Verwendung einer Tabelle. Ich habe meine Tests gemacht gcc verwenden und diesen Code und eine der einfachsten möglichen Szenario:
class Base {
public:
virtual void bark() { }
int dont_do_ebo;
};
class Derived1 : public Base {
public:
virtual void bark() { }
int dont_do_ebo;
};
class Derived2 : public Base {
public:
virtual void smile() { }
int dont_do_ebo;
};
void use(Base*);
int main() {
Base * b = new Derived1;
use(b);
Base * b1 = new Derived2;
use(b1);
}
Added Daten-Mitglieder den Compiler zu verhindern, dass die Basis-Klasse eine größen zu geben, von Null (es ist bekannt als die Leer-Basis-Klassen-Optimierung). Dies ist das Layout, das GCC gewählt: (drucken -fdump-Klasse-Hierarchie)
Vtable for Base
Base::_ZTV4Base: 3u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI4Base)
8 Base::bark
Class Base
size=8 align=4
base size=8 base align=4
Base (0xb7b578e8) 0
vptr=((& Base::_ZTV4Base) + 8u)
Vtable for Derived1
Derived1::_ZTV8Derived1: 3u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI8Derived1)
8 Derived1::bark
Class Derived1
size=12 align=4
base size=12 base align=4
Derived1 (0xb7ad6400) 0
vptr=((& Derived1::_ZTV8Derived1) + 8u)
Base (0xb7b57ac8) 0
primary-for Derived1 (0xb7ad6400)
Vtable for Derived2
Derived2::_ZTV8Derived2: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI8Derived2)
8 Base::bark
12 Derived2::smile
Class Derived2
size=12 align=4
base size=12 base align=4
Derived2 (0xb7ad64c0) 0
vptr=((& Derived2::_ZTV8Derived2) + 8u)
Base (0xb7b57c30) 0
primary-for Derived2 (0xb7ad64c0)
Wie Sie jede Klasse sehen eine VTable hat. Die ersten beiden Einträge sind speziell. Der zweite zeigt auf die RTTI-Daten der Klasse. Der erste - ich wusste es aber vergaß. Es hat etwas Verwendung in komplizierteren Fällen. Nun, wie das Layout zeigt, wenn Sie ein Objekt der Klasse Derived1 haben, dann zeigt der vptr (v-table-pointer) natürlich auf die v-Tabelle der Klasse Derived1, die genau einen Eintrag für ihre Funktion bark aufweist, die auf zeigt Derived1s Version. Der vptr von Derived2 verweist auf die vtable von Derived2, die zwei Einträge enthält. Der andere ist die neue Methode, die hinzugefügt wird, Lächeln. Es wiederholt den Eintrag für Base :: bark, der auf die Base-Version der Funktion natürlich zeigen wird, weil es die abgeleitete Version davon ist.
Ich habe auch den Baum, der von GCC generiert wird, nachdem einige Optimierungen durchgeführt wurden (Konstruktor inlined, ...), mit -fdump-tree-optimized. Der Ausgang wird mit GCC Mitte-End-Sprache GIMPL
, die Front-End-unabhängigen, eingekerbten in eine C-ähnlichen Blockstruktur ist:
;; Function virtual void Base::bark() (_ZN4Base4barkEv)
virtual void Base::bark() (this)
{
<bb 2>:
return;
}
;; Function virtual void Derived1::bark() (_ZN8Derived14barkEv)
virtual void Derived1::bark() (this)
{
<bb 2>:
return;
}
;; Function virtual void Derived2::smile() (_ZN8Derived25smileEv)
virtual void Derived2::smile() (this)
{
<bb 2>:
return;
}
;; Function int main() (main)
int main()()
{
void * D.1757;
struct Derived2 * D.1734;
void * D.1756;
struct Derived1 * D.1693;
<bb 2>:
D.1756 = operator new (12);
D.1693 = (struct Derived1 *) D.1756;
D.1693->D.1671._vptr.Base = &_ZTV8Derived1[2];
use (&D.1693->D.1671);
D.1757 = operator new (12);
D.1734 = (struct Derived2 *) D.1757;
D.1734->D.1682._vptr.Base = &_ZTV8Derived2[2];
use (&D.1734->D.1682);
return 0;
}
Wie wir schön sehen, es ist nur einen Zeiger einstellen - die vptr - das wird Zeigen Sie auf die entsprechende vtable, die wir bereits beim Erstellen des Objekts gesehen haben. Ich habe auch den Assembler-Code für die Erstellung der Derived1 und Aufruf ($ 4 ist das erste Argument-Register, $ 2 ist Rückgabewert-Register, $ 0 ist immer-0-Register) nach dem Ablehnen der Namen in ihm durch die c++filt
Werkzeug ausgegeben:
)
# 1st arg: 12byte
add $4, $0, 12
# allocate 12byte
jal operator new(unsigned long)
# get ptr to first function in the vtable of Derived1
add $3, $0, vtable for Derived1+8
# store that pointer at offset 0x0 of the object (vptr)
stw $3, $2, 0
# 1st arg is the address of the object
add $4, $0, $2
jal use(Base*)
Was passiert, wenn wir bark
anrufen möchten, geschieht:
void doit(Base* b) {
b->bark();
}
Gimpl Code:
;; Function void doit(Base*) (_Z4doitP4Base)
void doit(Base*) (b)
{
<bb 2>:
OBJ_TYPE_REF(*b->_vptr.Base;b->0) (b) [tail call];
return;
}
OBJ_TYPE_REF
ist ein GIMP L-Konstrukt, das in ziemlich gedruckt wird (es ist in gcc/tree.def
im gcc SVN Source-Code dokumentiert ist)
OBJ_TYPE_REF(<first arg>; <second arg> -> <third arg>)
Es bedeutet: Mit dem Ausdruck *b->_vptr.Base
auf dem Objekt b
, und speichern das Frontend (C++) spezifischen Wert 0
(es ist der Index in der vtable). Schließlich gibt es b
als "dieses" Argument. Würden wir eine Funktion aufrufen, die am zweiten Index in der vtable erscheint (Anmerkung, wir wissen nicht, welche vtable welchen Typs!), Würde die Gimpl wie folgt aussehen:
OBJ_TYPE_REF(*(b->_vptr.Base + 4);b->1) (b) [tail call];
Natürlich hier die Assembler-Code wieder (Stack-Frame-Material abgeschnitten):
# load vptr into register $2
# (remember $4 is the address of the object,
# doit's first arg)
ldw $2, $4, 0
# load whatever is stored there into register $2
ldw $2, $2, 0
# jump to that address. note that "this" is passed by $4
jalr $2
Denken Sie daran, dass die vptr Punkte genau an der ersten Funktion . (Vor diesem Eintrag wurde der RTTI-Slot gespeichert). Also, was immer an diesem Slot erscheint, wird aufgerufen. Es markiert auch den Aufruf als Tail-Call, weil es als letzte Anweisung in unserer doit
Funktion passiert.
Duplizieren? http://stackoverflow.com/questions/99297/at-as-deep-of-a-level-as-possible-how-are-virtual-functions-implementiert – Anonymous
Es gibt keine so genannte "virtuelle Klasse" in C++. – curiousguy