[英]Mismatch of 'this' address when base class is not polymorphic but derived is
有這個代碼:
#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
但是,當Derived類中的函數'fun'更改為virtual時:
virtual void fun(){} // changed in Derived
然后,'this'的地址在兩個構造函數中都不相同:
Base: 0xbf93d6a4
Derived: 0xbf93d6a0
另一件事是如果類Base是多態的,例如我添加了一些其他虛函數:
virtual void funOther(){} // added to Base
那么'this'匹配的地址再次:
Base: 0xbfcceda0
Derived: 0xbfcceda0
問題是 - 當Base類不是多態的並且Derived類是?時,為什么'this'地址在Base和Derived類中是不同的?
當您具有類的多態單繼承層次結構時,大多數(如果不是全部)編譯器遵循的典型約定是該層次結構中的每個對象必須以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
這看起來像是對象中具有v表指針的多態的典型實現的行為。 Base類不需要這樣的指針,因為它沒有任何虛方法。 這樣可以在32位計算機上保存對象大小的4個字節。 典型的布局是:
+------+------+------+
| x | y | z |
+------+------+------+
^
| this
但是Derived類確實需要v表指針。 通常存儲在對象布局中的偏移0處。
+------+------+------+------+
| vptr | x | y | z |
+------+------+------+------+
^
| this
因此,為了使Base類方法看到對象的相同布局,代碼生成器在調用Base類的方法之前將4添加到this指針。 構造函數看到:
+------+------+------+------+
| vptr | x | y | z |
+------+------+------+------+
^
| this
這解釋了為什么你看到4添加到Base構造函數中的this指針值。
從技術上講, 這正是發生的事情。
但是必須注意的是,根據語言規范,多態的實現並不一定與vtable有關:這就是規范。 定義為“實現細節”,這超出了規范范圍。
我們可以說的是, this
有一個類型,並指向可通過其類型訪問的內容。 再次引用成員的方式是一個實現細節。
一個事實pointer to something
轉換成時,一個pointer to something else
,無論是隱含的,靜態或動態轉換,必須要改變,以適應周圍的事物必須考慮的規則 ,而不是例外 。
順便說一下C ++的定義方式,問題就像答案一樣毫無意義,因為他們隱含地假設實現是基於假設的布局。
在給定情況下,兩個對象子組件共享相同原點的事實只是(非常常見的)特定情況。
例外是“重新解釋”:當你“盲目”類型系統時,只是說“查看這一堆字節,因為它們是這種類型的實例”:這是唯一的情況,你必須期望沒有地址變化(並且沒有責任)從編譯器關於這種轉換的意義)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.