簡體   English   中英

當基類不是多態而是派生時,'this'地址不匹配

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM