我不確定 André Caron 在這里是什么意思:
C中的虛函式
...其中一些代碼依賴于(官方)“恰好”在大多數編譯器上作業的非標準行為。主要問題是代碼假定 &m.base == &m (例如,基本成員的偏移量為 0)。如果不是這種情況,則 custom_bar() 中的強制轉換會導致未定義的行為。要解決此問題,您可以在 struct foo 中添加一個額外的指標,如下所示:
m是型別struct meh *。型別的物件 fstruct foo *通過強制轉換分配給 m struct meh *。
struct meh具有base型別struct foo( struct foo meh::base = foo::bar) 的成員。為什么據說不能保證&m.base == &m?如果結構不是 POD,我可以看到這一點。安德烈也暗示了這一點。但是,為什么 POD 結構必須有另一個指標void *foo::hook?
struct meh * m = (struct meh*)f;變成struct meh * m = (struct meh*)f->hook;. 在他指派hook之后m->base.hook = m;。
struct meh
{
/* inherit from "class foo". MUST be first. */
struct foo base;
int more_data;
};
下面,我列出了我研究的相關 ISO C90/C 98 節選。我還創建了一個代碼示例。示例代碼可以通過 Clang 編譯-fsanitize=undefined -std=c 98 -O0 -Wall -Wextra -Wpedantic -Wconversion -Wundef。
這里是:
https://godbolt.org/z/qo9f8KnYM
摘錄
來自 ISO C90 (ANSI C89):
物件的存盤值只能由具有以下型別之一的左值訪問:/28/
...
- 聚合或聯合型別,在其成員中包括上述型別之一(遞回地包括子聚合或
包含聯合的成員),或
一個指向結構物件的指標,經過適當的轉換,指向它的初始成員(或者如果該成員是位域,則指向它所在的單元),反之亦然。因此,結構物件內可能存在未命名的孔,但不是在其開始處,這是實作適當對齊所必需的。
來自 ISO C 98:
16 如果一個 POD-union 包含兩個或多個 POD-structs,它們共享一個共同的初始序列,并且如果 POD-union 物件當前包含這些 POD-structs 之一,則允許檢查其中任何一個的共同初始部分。如果對應的成員具有一個或多個初始成員序列的布局兼容型別(并且對于位域,具有相同的寬度),則兩個 POD 結構共享一個公共初始序列。17 指向 POD-struct 物件的指標,使用 reinterpret_cast 適當轉換,指向其初始成員(或者如果該成員是位欄位,則指向它所在的單元),反之亦然。[注意:因此,在 POD 結構物件中可能存在未命名的填充,但不是在其開頭,這是實作適當對齊所必需的。]
代碼示例
#include <iostream>
struct A {
int m1;
};
struct B {
int m1;
int m2;
};
struct C {
struct A super;
int m3;
};
int main(void) {
struct A a = {42};
struct C c = {{666}, 1984};
// Access A::m1 through pointer of type B
std::cout << ((B *)&a)->m1 << std::endl; // 42
// Access A::m1 through pointer of type C
std::cout << ((C *)&a)->super.m1 << std::endl; // 42
// Access C::super::A::m1 through pointer of type A.
std::cout << ((A *)(&c))->m1 << std::endl; // 666
return 0;
}
編輯1:讓我在這個編輯部分重寫這個問題。我將忽略 C ,因為評論中的人告訴我不要使問題復雜化。如果這個編輯比原來的更有幫助,那么也許你可以考慮用這個編輯替換原來的帖子。或者我或其他人可以“洗掉”原來的那個。或者,如果您對如何改進我的問題有更好的想法,請告訴我。(我可能會補充一點,我的注意力有問題,很容易迷失在細節上......我會留在那里。你可能已經猜到它是什么......)如果我的第二次嘗試仍然失敗,那么也許我應該把我沒有提出一個明確的問題作為暗示,如果適用的話,下次再思考和寫下來。事不宜遲,這是我第二次嘗試提出這個問題:
我指的是此處發布的答案:
C中的虛函式
struct Base {
int x;
};
struct Derived {
struct Base super;
};
如果offsetof(struct Derived, super) == 0和offsetof(struct Base, x) == 0,那么我們可以暗示offsetof(struct Derived, super.x) == offsetof(struct Base, x)嗎?
André Caron 建議使用指向派生物件的額外指標。顯然,僅依靠offsetof(struct Derived, super.x) == offsetof(struct Base, x).
即使這可行,您也依賴編譯器擴展來進行型別雙關語,這可能導致未定義的行為blablabla。事實上,這在 GCC 和 MSVC 中有效。
事實上,對齊的東西依賴于編譯器擴展。您可以使用 struct foo 中指向“派生物件”的額外 void* 指標使其可移植。然而,該技術在眾所周知的庫中非常流行,被認為是“便攜的”。任何使這種型別的代碼中斷的編譯器都會受到客戶的大量投訴。
我很難理解為什么offsetof(struct Derived, super.x) != offsetof(struct Base, x)可能會出現這種情況。我沒有在 C 標準中找到澄清。因此,我正在尋求進一步的澄清。
13:26,重申我的假設:
假設offsetof(struct Derived, super.x) != offsetof(struct Base, x)
struct Base {
int x;
void *hook;
};
struct Derived {
struct Base super;
};
有了上面的假設,考慮:
struct Base base = {42};
struct Derived derived;
base.hook = &base; /* Assuming offsetof(struct Base, x) == 0 */
derived.super = base;
(struct Base*)(derived.super.hook) == &base應該是真的。
#include <stddef.h>
#include <stdio.h>
struct Base {
int x;
void *hook;
};
struct Derived {
struct Base super;
};
int main(void) {
struct Base base = {42};
struct Derived derived;
base.hook = &base; /* Assuming offsetof(struct Base, x) == 0 */
derived.super = base;
printf("Offset Base x: %lu\n", offsetof(struct Base, x));
printf("Offset Derived super: %lu\n", offsetof(struct Derived, super));
printf("Offset Derived super.x: %lu\n", offsetof(struct Derived, super.x));
printf("Offset Derived super.hook: %lu\n",
offsetof(struct Derived, super.hook));
printf("derived.super.hook == &base, yields %d",
(struct Base *)(derived.super.hook) == &base);
return 0;
}
uj5u.com熱心網友回復:
但是,為什么 POD 結構必須有另一個指標 void *foo::hook?
這是沒有必要的。從原始問題和答案:
這種技術更可靠,特別是如果您打算用 C 撰寫“派生結構”并使用虛函式。在這種情況下,第一個成員的偏移量通常為非 0,因為編譯器將運行時型別資訊和類的 v-table 存盤在那里。
具有虛函式的 c 結構/類不是 POD。任何非 POD 結構/類的資料成員都可以有一個非 0 偏移量,這就是hook需要處理的情況。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/474095.html
