一个类的VTT是什么? [英] What is the VTT for a class?
问题描述
最近遇到了一个新的C ++链接器错误。
Recently ran across a C++ linker error that was new to me.
libfoo.so: undefined reference to `VTT for Foo'
libfoo.so: undefined reference to `vtable for Foo'
错误和修复我的问题,但我仍然有一个gingging的问题:什么是VTT?
I recognized the error and fixed my problem, but I still have a nagging question: what exactly is a VTT?
Aside:发生在你忘记定义在类中声明的第一个虚拟函数时。 vtable进入类的第一个虚函数的编译单元。如果你忘记定义该函数,你会得到一个链接器错误,它找不到vtable,而不是更加友好的开发人员无法找到该函数。
Aside: For those interested, the problem occurs when you forget to define the first virtual function declared in a class. The vtable goes into the compilation unit of the class's first virtual function. If you forget to define that function, you get a linker error that it can't find the vtable rather than the much more developer-friendly can't find the function.
推荐答案
页面GCC C ++编译器v4中多重继承的注意事项.0.1目前处于离线状态, http://web.archive.org 未对其进行归档。因此,我在 tinydrblog 找到了一份文档副本,该文档已存档在网络存档。
The page "Notes on Multiple Inheritance in GCC C++ Compiler v4.0.1" is offline now, and http://web.archive.org didn't archive it. So, I have found a copy of the text at tinydrblog which is archived at the web archive.
是原始Notes的全文,作为博士编程语言研讨会:GCC内部(2005年秋季)由Morgan Deters研究生在圣路易斯华盛顿大学计算机科学系的分布式对象计算实验室。
他(已归档)首页:
There is full text of the original Notes, published online as part of "Doctoral Programming Language Seminar: GCC Internals" (fall 2005) by graduate Morgan Deters "in the Distributed Object Computing Laboratory in the Computer Science department at Washington University in St Louis."
His (archived) homepage:
THIS IS THE TEXT by Morgan Deters and NOT CC-licensed. PART1:
基础:单一继承
正如我们在类中讨论的,单继承导致一个对象布局,其中基类数据在派生类数据之前布置。因此,如果类 A
和 B
定义如下:
As we discussed in class, single inheritance leads to an object layout with base class data laid out before derived class data. So if classes A
and B
are defined thusly:
class A {
public:
int a;
};
class B : public A {
public:
int b;
};
,则类型为 B
像这样(其中b是指向这样的对象的指针):
then objects of type B
are laid out like this (where "b" is a pointer to such an object):
b --> +-----------+
| a |
+-----------+
| b |
+-----------+
方法:
class A {
public:
int a;
virtual void v();
};
class B : public A {
public:
int b;
};
那么你还有一个vtable指针:
then you'll have a vtable pointer as well:
+-----------------------+
| 0 (top_offset) |
+-----------------------+
b --> +----------+ | ptr to typeinfo for B |
| vtable |-------> +-----------------------+
+----------+ | A::v() |
| a | +-----------------------+
+----------+
| b |
+----------+
简单多重继承
Simple Multiple Inheritance
现在考虑多重继承:
class A {
public:
int a;
virtual void v();
};
class B {
public:
int b;
virtual void w();
};
class C : public A, public B {
public:
int c;
};
在这种情况下,类型C的对象布局如下:
In this case, objects of type C are laid out like this:
+-----------------------+
| 0 (top_offset) |
+-----------------------+
c --> +----------+ | ptr to typeinfo for C |
| vtable |-------> +-----------------------+
+----------+ | A::v() |
| a | +-----------------------+
+----------+ | -8 (top_offset) |
| vtable |---+ +-----------------------+
+----------+ | | ptr to typeinfo for C |
| b | +---> +-----------------------+
+----------+ | B::w() |
| c | +-----------------------+
+----------+
...但是为什么?为什么两个vtables在一个?好了,想想类型替换。如果我有一个指向C的指针,我可以将它传递给一个函数,期望一个指针到A或一个函数,期望一个指针到B。如果一个函数期望一个指向A的指针,并且我想传递我的变量c(类型指针到C)的值,我已经设置。可以通过(第一个)vtable调用 A :: v()
,被调用的函数可以通过指针访问成员a,它可以通过任何指针到A。
...but why? Why two vtables in one? Well, think about type substitution. If I have a pointer-to-C, I can pass it to a function that expects a pointer-to-A or to a function that expects a pointer-to-B. If a function expects a pointer-to-A and I want to pass it the value of my variable c (of type pointer-to-C), I'm already set. Calls to A::v()
can be made through the(first) vtable, and the called function can access the member a through the pointer I pass in the same way as it can through any pointer-to-A.
但是,如果我传递的指针变量 c
到一个指向B的指针的函数,我们还需要一个类型为B的子对象来引用它。这就是为什么我们有第二个vtable指针。我们可以将指针值(c + 8字节)传递给需要一个指向B的指针的函数,它的所有设置:它可以调用 B :: w()
通过(第二个)vtable指针,并通过指针访问成员b,我们以相同的方式通过任何指针到达B。
However, if I pass the value of my pointer variable c
to a function that expects a pointer-to-B, we also need a subobject of type B in our C to refer it to. This is why we have the second vtable pointer. We can pass the pointer value(c + 8 bytes) to the function that expects a pointer-to-B, and it's all set: it can make calls to B::w()
through the (second) vtable pointer, and access the member b through the pointer we pass in the same way as it can through any pointer-to-B.
请注意,这种指针校正需要发生在被调用的方法。在这种情况下,类 C
继承 B :: w()
。当通过指向C的指针调用 w()
时,指针(它成为 w()
需要调整,这通常被称为这个指针调整。
Note that this "pointer-correction" needs to occur for called methods too. Class C
inherits B::w()
in this case. When w()
is called on through a pointer-to-C, the pointer (which becomes the this pointer inside of w()
needs to be adjusted. This is often called this pointer adjustment.
在某些情况下,编译器会生成一个thunk来修复地址。与上述代码相同,但此时 C
覆盖 B
的成员函数 w / code>:
In some cases, the compiler will generate a thunk to fix up the address. Consider the same code as above but this time C
overrides B
's member function w()
:
class A {
public:
int a;
virtual void v();
};
class B {
public:
int b;
virtual void w();
};
class C : public A, public B {
public:
int c;
void w();
};
的对象布局和vtable现在看起来像这:
C
's object layout and vtable now look like this:
+-----------------------+
| 0 (top_offset) |
+-----------------------+
c --> +----------+ | ptr to typeinfo for C |
| vtable |-------> +-----------------------+
+----------+ | A::v() |
| a | +-----------------------+
+----------+ | C::w() |
| vtable |---+ +-----------------------+
+----------+ | | -8 (top_offset) |
| b | | +-----------------------+
+----------+ | | ptr to typeinfo for C |
| c | +---> +-----------------------+
+----------+ | thunk to C::w() |
+-----------------------+
现在,当通过指针在 C
的实例上调用 w()
to-B,thunk被调用。 thunk是什么?让我们反汇编它(这里, gdb
):
Now, when w()
is called on an instance of C
through a pointer-to-B, the thunk is called. What does the thunk do? Let's disassemble it (here, with gdb
):
0x0804860c <_ZThn8_N1C1wEv+0>: addl $0xfffffff8,0x4(%esp)
0x08048611 <_ZThn8_N1C1wEv+5>: jmp 0x804853c <_ZN1C1wEv>
所以它只是调整 this
跳转到 C :: w()
。一切都很好。
So it merely adjusts the this
pointer and jumps to C::w()
. All is well.
但不是上面的意思是 B
的vtable总是指向这个 C :: w()
thunk?我的意思是,如果我们有一个指向B的合法的 B
(不是 C
),不想调用thunk,对吗?
But doesn't the above mean that B
's vtable always points to this C::w()
thunk? I mean, if we have a pointer-to-B that is legitimately a B
(not a C
), we don't want to invoke the thunk, right?
右键。在 C
中的 B
的上述嵌入式vtable对于B-in-C情况是特殊的。 B的常规vtable是正常的,并且直接指向 B :: w()
。
Right. The above embedded vtable for B
in C
is special to the B-in-C case. B's regular vtable is normal and points to B::w()
directly.
基本类别的多个副本(非虚拟继承)
The Diamond: Multiple Copies of Base Classes (non-virtual inheritance)
好的。现在来处理真的很难的东西。在形成继承菱形时回忆基类的多个副本的常见问题:
Okay. Now to tackle the really hard stuff. Recall the usual problem of multiple copies of base classes when forming an inheritance diamond:
class A {
public:
int a;
virtual void v();
};
class B : public A {
public:
int b;
virtual void w();
};
class C : public A {
public:
int c;
virtual void x();
};
class D : public B, public C {
public:
int d;
virtual void y();
};
请注意, D
c $ c> B 和 C
和 B
C 都继承自 A
。这意味着 D
有两个副本 A
。对象布局和vtable嵌入是我们从前面的部分所期望的:
Note that D
inherits from both B
and C
, and B
and C
both inherit from A
. This means that D
has two copies of A
in it. The object layout and vtable embedding is what we would expect from the previous sections:
+-----------------------+
| 0 (top_offset) |
+-----------------------+
d --> +----------+ | ptr to typeinfo for D |
| vtable |-------> +-----------------------+
+----------+ | A::v() |
| a | +-----------------------+
+----------+ | B::w() |
| b | +-----------------------+
+----------+ | D::y() |
| vtable |---+ +-----------------------+
+----------+ | | -12 (top_offset) |
| a | | +-----------------------+
+----------+ | | ptr to typeinfo for D |
| c | +---> +-----------------------+
+----------+ | A::v() |
| d | +-----------------------+
+----------+ | C::x() |
+-----------------------+
当然,我们期望存在 A
的数据(成员 a
两次在 D
的对象布局(和它是),我们期望 A
的虚拟成员函数在vtable中表示两次( A :: v()
确实存在)。
Of course, we expect A
's data (the member a
) to exist twice in D
's object layout (and it is), and we expect A
's virtual member functions to be represented twice in the vtable (and A::v()
is indeed there). Okay, nothing new here.
钻石:单一虚拟基地副本
但是如果我们应用虚拟继承呢? C ++虚拟继承允许我们指定一个菱形层次,但是保证只有一个虚拟继承的基础副本。所以让我们以这种方式编写代码:
But what if we apply virtual inheritance? C++ virtual inheritance allows us to specify a diamond hierarchy but be guaranteed only one copy of virtually inherited bases. So let's write our code this way:
class A {
public:
int a;
virtual void v();
};
class B : public virtual A {
public:
int b;
virtual void w();
};
class C : public virtual A {
public:
int c;
virtual void x();
};
class D : public B, public C {
public:
int d;
virtual void y();
};
突然之间的事情变得更复杂。如果我们在 D
的表示中只能有一个副本 A
,那么我们就无法再使用我们的在 D
中嵌入 C
(并嵌入 C
D
的部分 D
)。但是,如果我们不能这样做,我们如何处理通常的类型替换呢?
All of a sudden things get a lot more complicated. If we can only have one copy of A
in our representation of D
, then we can no longer get away with our "trick" of embedding a C
in a D
(and embedding a vtable for the C
part of D
in D
's vtable). But how can we handle the usual type substitution if we can't do this?
让我们尝试绘制布局:
+-----------------------+
| 20 (vbase_offset) |
+-----------------------+
| 0 (top_offset) |
+-----------------------+
| ptr to typeinfo for D |
+----------> +-----------------------+
d --> +----------+ | | B::w() |
| vtable |----+ +-----------------------+
+----------+ | D::y() |
| b | +-----------------------+
+----------+ | 12 (vbase_offset) |
| vtable |---------+ +-----------------------+
+----------+ | | -8 (top_offset) |
| c | | +-----------------------+
+----------+ | | ptr to typeinfo for D |
| d | +-----> +-----------------------+
+----------+ | C::x() |
| vtable |----+ +-----------------------+
+----------+ | | 0 (vbase_offset) |
| a | | +-----------------------+
+----------+ | | -20 (top_offset) |
| +-----------------------+
| | ptr to typeinfo for D |
+----------> +-----------------------+
| A::v() |
+-----------------------+
好吧。因此,您看到 A
现在嵌入在 D
中,其基本方式与其他基数相同。但它嵌入在D而不是直接派生类。
Okay. So you see that A
is now embedded in D
in essentially the same way that other bases are. But it's embedded in D rather than inits directly-derived classes.
这篇关于一个类的VTT是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!