下面是这段代码:
#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;
}输出:
Base: 0xbfdb81d4
Derived: 0xbfdb81d4但是,当在派生类中将函数“fun”更改为virtual时:
virtual void fun(){} // changed in Derived那么在两个构造函数中'this‘的地址是不同的:
Base: 0xbf93d6a4
Derived: 0xbf93d6a0另一件事是如果基类是多态的,例如我在那里添加了一些其他的虚函数:
virtual void funOther(){} // added to Base那么两个'this‘的地址再次匹配:
Base: 0xbfcceda0
Derived: 0xbfcceda0问题是-当基类不是多态的而派生类是多态的时候,为什么'this‘地址在基类和派生类中是不同的?
发布于 2012-07-22 00:25:22
当您拥有多态的单继承类层次结构时,大多数(如果不是全部)编译器遵循的典型约定是,该层次结构中的每个对象都必须以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 |
+------------------------------------+同时,层次结构中非多态(上部)部分的所有类应该对任何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发布于 2012-07-22 00:35:52
这看起来像是在对象中使用v-table指针的多态性的典型实现的行为。Base类不需要这样的指针,因为它没有任何虚方法。这在32位机器上节省了4个字节的对象大小。典型的布局是:
+------+------+------+
| x | y | z |
+------+------+------+
^
| this然而,派生类确实需要v-table指针。通常存储在对象布局中的偏移量0处。
+------+------+------+------+
| vptr | x | y | z |
+------+------+------+------+
^
| this因此,为了使基类方法看到对象的相同布局,代码生成器在调用基类的方法之前向this指针添加4。构造函数看到:
+------+------+------+------+
| vptr | x | y | z |
+------+------+------+------+
^
| this这就解释了为什么在Base构造函数中的this指针值中添加了4。
发布于 2012-07-22 01:24:59
从技术上讲,this就是所发生的事情。
然而,必须注意的是,根据语言规范,多态性的实现不一定与vtable相关:这就是规范。定义为“实现细节”,这超出了规范的范围。
我们所能说的就是this有一个类型,并指向通过它的类型可以访问的内容。取消对成员的引用是如何发生的,同样是一个实现细节。
事实上,当通过隐式、静态或动态转换将pointer to something转换为pointer to something else时,必须进行更改以适应周围的情况,这一事实必须被视为规则,而不是例外。
按照定义C++的方式,这个问题和答案一样没有意义,因为它们隐含地假设实现是基于假定的布局。
事实上,在给定的情况下,两个对象子组件共享同一原点,这只是一个(非常常见的)特殊情况。
例外是“重新解释”:当你“看不见”类型系统,只说“看这一堆字节,因为它们是这个类型的一个实例”:这是唯一的一种情况,你不需要期待地址改变(编译器也不会对这种转换的意义负责)。
https://stackoverflow.com/questions/11593783
复制相似问题