簡體   English   中英

為什么我們需要“this pointer adjustor thunk”?

[英]Why do we need "this pointer adjustor thunk"?

我從這里讀到有關調節器的信息。 這是一些引述:

現在,只有一個 QueryInterface 方法,但是有兩個條目,每個 vtable 一個。 請記住,vtable 中的每個 function 都接收相應的接口指針作為其“this”參數。 這對 QueryInterface (1) 來說很好; 它的接口指針與對象的接口指針相同。 但這對 QueryInterface (2) 來說是個壞消息,因為它的接口指針是 q,而不是 p。

這就是調節器的用武之地。

我想知道為什么“ vtable 中的每個 function 都接收相應的接口指針作為其“this”參數”? 它是接口方法用於在 object 實例中定位數據成員的唯一線索(基址)嗎?

更新

這是我最新的理解:

其實我的問題不是這個參數的用途,而是為什么要用對應的接口指針作為這個參數。 抱歉我的含糊不清。

除了將界面指針用作對象布局中的定位器/立足點之外。 當然還有其他方法可以做到這一點,只要您是組件的實現者。

但是對於我們組件的客戶來說,情況並非如此。

當組件以 COM 方式構建時,我們組件的客戶對我們組件的內部結構一無所知。 客戶端只能獲取接口指針,而 this 正是將作為this參數傳遞到接口方法中的指針 在這種期望下,編譯器只好根據這個特定的this指針來生成接口方法的代碼。

因此,上述推理導致的結果是:

必須確保 vtable 中的每個 function 都必須接收相應的接口指針作為其“ this ”參數。

在“this pointer adjustor thunk”的情況下,單個 QueryInterface() 方法存在 2 個不同的條目,換句話說,可以使用 2 個不同的接口指針來調用 QueryInterface() 方法,但編譯器只生成 1 個副本查詢接口()方法。 因此,如果其中一個接口被編譯器選擇為 this 指針,我們需要將另一個接口調整為所選擇的接口。 這就是這款調節器的誕生之處。

順便說一句,如果編譯器可以生成 2 個不同的 QueryInterface() 方法實例怎么辦? 每一個都基於相應的接口指針。 這不需要調整器 thunk,但需要更多空間來存儲額外但相似的代碼。

BTW-2:似乎有時一個問題從實現者的角度缺乏合理的解釋,但從用戶的角度可以更好地理解。

從這個問題收走了COM的一部分, this指針調節thunk是一段代碼,使得確保每個函數獲取this指針指向的具體類型的子對象。 該問題出現了多重繼承,其中基礎對象和派生對象未對齊。

請考慮以下代碼:

struct base {
   int value;
   virtual void foo() { std::cout << value << std::endl; }
   virtual void bar() { std::cout << value << std::endl; }
};
struct offset {
   char space[10];
};
struct derived : offset, base {
   int dvalue;
   virtual void foo() { std::cout << value << "," << dvalue << std::endl; }
};

(並且無視初始化的缺乏)。 derivedbase子對象不與對象的開始對齊,因為在[1]之間存在offset 當一個指向derived的指針被轉換為指向base的指針(包括隱式轉換,但沒有重新解釋會導致UB和潛在死亡的轉換)時,指針的值會被偏移,因此(void*)d != (void*)((base*)d)對於derived類型的假定對象d

現在考慮一下用法:

derived d;
base * b = &d; // This generates an offset
b->bar();
b->foo();

base指針或引用調用函數時會出現問題。 如果虛擬調度機制發現最終置換器是在base ,則指針this必須提及的base對象,如b->bar ,其中,所述隱式this指針是存儲在同一地址b 現在,如果最終覆蓋在派生類中,與b->foo()this指針必須與找到最終覆蓋的類型的子對象的開頭對齊(在本例中為derived )。

編譯器所做的是創建一段中間代碼。 當調用虛擬調度機制時,在調度到derived::foo ,中間調用接受this指針並將偏移量減去derived對象的開頭。 此操作與downcast static_cast<derived*>(this) 請記住,此時, this指針的類型為base ,因此它最初是偏移的,這有效地返回原始值&d

[1] 即使在接口的情況下也存在偏移 - 在Java / C#意義上:僅定義虛擬方法的類 - 因為它們需要將表存儲到該接口的vtable。

是一篇關於MSVC內部設計師的文章。 它解釋了MSVC實現的許多其他細節。 您可能還想查看我在OpenRCE上關於它在裝配中的外觀的文章。

它是接口方法用於在對象實例中定位數據成員的唯一線索(基址)嗎?

是的,這就是真的。

是的, this對於找到對象的起始位置至關重要。 你寫下你的代碼:

variable = 10;

其中variable是成員變量。 首先,它屬於哪個對象? 它屬於this指針指向的對象。 實際上就是這樣

this->variable = 10;

現在C ++需要生成能夠實現工作的代碼 - 復制數據。 為了做到這一點,它需要知道對象start和成員變量之間的偏移量。 慣例是, this始終指向對象的開始,因此偏移量可以是常量:

*(reinterpret_cast<int*>( reinterpret_cast<char*>( this ) + variableOffset ) ) = 10; //assuming variable is of type int

我認為重要的是要指出在C ++中沒有“接口指針”這樣的實體或者接近它的任何東西。 它最多建立在受限制的抽象類的概念上,但仍然是一個類。 因此,適用於班級成員和處理“此”的所有規則仍然適用不變。 所以主要是接口類必須表現為給定類型的獨立類,而不管它們的功能和最終的繼承層次結構。

我們可以使用虛方法調用機制來獲取(接口)基類公開的對象的實際(動態類型)。 它是如何完成的是具體實現,包括虛擬方法表和“調整器thunks”等概念。 通常編譯器可以使用其初始的'this'指針來定位VMT,然后使用給定函數的實際實現,並通過最終調整'this'指針來調用它。 如果基類的內存布局不同於在多重繼承的情況下我們持有的引用的派生布局,則通常需要thunk調整來執行最終調用。

沒有足夠的代表發表評論,但 OP 中的鏈接已損壞。 我假設內容現在在這里可用:

https://devblogs.microsoft.com/oldnewthing/20040206-00/?p=40723

任何人都可以更新 OP 並刪除這個答案嗎?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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