scd*_*dmb 9 c++ polymorphism inheritance memory-layout
有这个代码:
#include <iostream>
class Base
{
public:
Base() {
std::cout << "Base: " << this << std::endl;
}
int x;
int y;
int z;
};
class Derived : Base
{
public:
Derived() {
std::cout << "Derived: " << this << std::endl;
}
void fun(){}
};
int main() {
Derived d;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
输出:
Base: 0xbfdb81d4
Derived: 0xbfdb81d4
Run Code Online (Sandbox Code Playgroud)
但是,当Derived类中的函数'fun'更改为virtual时:
virtual void fun(){} // changed in Derived
Run Code Online (Sandbox Code Playgroud)
然后,'this'的地址在两个构造函数中都不相同:
Base: 0xbf93d6a4
Derived: 0xbf93d6a0
Run Code Online (Sandbox Code Playgroud)
另一件事是如果类Base是多态的,例如我添加了一些其他虚函数:
virtual void funOther(){} // added to Base
Run Code Online (Sandbox Code Playgroud)
那么'this'匹配的地址再次:
Base: 0xbfcceda0
Derived: 0xbfcceda0
Run Code Online (Sandbox Code Playgroud)
问题是 - 当Base类不是多态的并且Derived类是?时,为什么'this'地址在Base和Derived类中是不同的?
AnT*_*AnT 14
当您具有类的多态单继承层次结构时,大多数(如果不是全部)编译器遵循的典型约定是该层次结构中的每个对象必须以VMT指针(指向虚方法表的指针)开头.在这种情况下,VMT指针很早就被引入到对象内存布局中:通过多态层次结构的根类,而所有下层类只是继承它并将其设置为指向它们正确的VMT.在这种情况下,任何派生对象中的所有嵌套子对象都具有相同的this值.通过读取*this编译器的内存位置,这种方式可以立即访问VMT指针,而不管实际的子对象类型如何.这正是您上次实验中发生的情况.当您使根类具有多态性时,所有this值都匹配.
但是,当层次结构中的基类不是多态时,它不会引入VMT指针.VMT指针将由层次结构中较低位置的第一个多态类引入.在这种情况下,流行的实现方法是在由层次结构的非多态(上部)部分引入的数据之前插入VMT指针.这是您在第二个实验中看到的内容.内存布局Derived如下
+------------------------------------+ <---- `this` value for `Derived` and below
| VMT pointer introduced by Derived |
+------------------------------------+ <---- `this` value for `Base` and above
| Base data |
+------------------------------------+
| Derived data |
+------------------------------------+
Run Code Online (Sandbox Code Playgroud)
同时,层次结构的非多态(上层)部分中的所有类都不应该知道任何VMT指针.Base类型对象必须以数据字段开头Base::x.同时,层次结构的多态(较低)部分中的所有类必须以VMT指针开头.为了满足这两个要求,编译器被迫调整对象指针值,因为它在层次结构中从一个嵌套的基础子对象上下转换为另一个.这立即意味着跨多态/非多态边界的指针转换不再是概念性的:编译器必须添加或减去一些偏移量.
来自层次结构的非多态部分的子对象将共享它们的this值,而来自层次结构的多态部分的子对象将共享它们自己的不同this值.
在沿层次结构转换指针值时必须添加或减去一些偏移量并不罕见:编译器必须在处理多继承层次结构时始终执行此操作.但是,您的示例显示了如何在单继承层次结构中实现它.
加法/减法效果也将在指针转换中显示
Derived *pd = new Derived;
Base *pb = pd;
// Numerical values of `pb` and `pd` are different if `Base` is non-polymorphic
// and `Derived` is polymorphic
Derived *pd2 = static_cast<Derived *>(pb);
// Numerical values of `pd` and `pd2` are the same
Run Code Online (Sandbox Code Playgroud)
这看起来像是对象中具有v表指针的多态的典型实现的行为.Base类不需要这样的指针,因为它没有任何虚方法.这样可以在32位计算机上保存对象大小的4个字节.典型的布局是:
+------+------+------+
| x | y | z |
+------+------+------+
^
| this
Run Code Online (Sandbox Code Playgroud)
但是Derived类确实需要v表指针.通常存储在对象布局中的偏移0处.
+------+------+------+------+
| vptr | x | y | z |
+------+------+------+------+
^
| this
Run Code Online (Sandbox Code Playgroud)
因此,为了使Base类方法看到对象的相同布局,代码生成器在调用Base类的方法之前将4添加到this指针.构造函数看到:
+------+------+------+------+
| vptr | x | y | z |
+------+------+------+------+
^
| this
Run Code Online (Sandbox Code Playgroud)
这解释了为什么你看到4添加到Base构造函数中的this指针值.
| 归档时间: |
|
| 查看次数: |
1803 次 |
| 最近记录: |