我正在閱讀Don Clugston的文章Member Function Pointers and the Fastest possible C Delegates并且自己正在試驗這些東西,但無法正確重現案例。
當然,Don Clugston 的代碼是未定義的行為。
這特別是關于 GCC 對成員函式指標的表示。
這是關于 GCC 成員函式表示的文章中的一段代碼片段(從文章中照原樣復制,不是實際代碼,甚至不要編譯):
// GNU g uses a tricky space optimisation, also adopted by IBM's VisualAge and XLC. struct GnuMFP { union { CODEPTR funcadr; // always even int vtable_index_2; // = vindex*2 1, always odd }; int delta; }; adjustedthis = this delta if (funcadr & 1) CALL (* ( *delta (vindex 1)/2) 4) else CALL funcadr
當然,標準沒有說明這一點。此外,自本文撰寫以來,GCC ABI 可能已經發生了很大變化。但是,我對標準或定義的行為不感興趣。我對當前的 ABI 和編譯器的作用很感興趣。
問題是我無法生成一個成員函式指標來填充delta值以供我試驗。
我假設類似的東西delta仍然存在,因為成員函式指標的大小仍然是兩個指標的大小。另外,根據我的觀察,vtable 索引技巧今天仍然適用。
這是我嘗試過的:
#include <cstring>
#include <iostream>
#include <iomanip>
void print_pointer(auto const ptr) {
alignas(alignof(ptr)) std::byte memory[sizeof(ptr)];
std::memcpy(memory, std::addressof(ptr), sizeof(ptr));
auto until_newline = int{8};
for (auto const b : memory) {
std::cout << std::hex << std::setfill('0') << std::setw(2) << static_cast<std::uint16_t>(b);
if (--until_newline == 0) {
until_newline = 8;
std::cout << '\n';
}
}
}
// No inheritance, simplest possible
namespace test1 {
struct S {
char a;
void method() const& {
std::cout << "test1 S";
}
};
}
// Simple inheritance, non polymorphic
namespace test2 {
struct B1 { char a; };
struct S : B1 {
char a;
void method() const& {
std::cout << "test1 S";
}
};
}
// Multiple inheritance, non polymorphic
namespace test3 {
struct B1 { char a; };
struct B2 { char a; };
struct S : B1, B2 {
char a;
void method() const& {
std::cout << "test1 S";
}
};
}
// Multiple inheritance, non polymorphic, function in the middle
namespace test4 {
struct B1 { char a; };
struct B2 {
char a;
void method() const& {
std::cout << "test1 S";
}
};
struct S : B1, B2 { char a; };
}
// Simple inheritance, polymorphic
namespace test5 {
struct B1 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct S : B1 {
void method() const& override {
std::cout << "test1 S";
}
};
}
// Multiple inheritance, polymorphic, one base only
namespace test6 {
struct B1 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct B2 {
char a;
};
struct S : B1, B2 {
void method() const& override {
std::cout << "test1 S";
}
};
}
// Multiple inheritance, polymorphic, two base
namespace test7 {
struct B1 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct B2 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct S : B1, B2 {
void method() const& override {
std::cout << "test1 S";
}
};
}
// Simple virtual inheritance, non polymorphic
namespace test8 {
struct B1 { char a; };
struct S : virtual B1 {
void method() const& {
std::cout << "test1 S";
}
};
}
// Simple virtual inheritance, polymorphic
namespace test9 {
struct B1 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct S : virtual B1 {
void method() const& override {
std::cout << "test1 S";
}
};
}
// Multiple with one virtual inheritance, one polymorphic
namespace test10 {
struct B1 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct B2 {
char a;
};
struct S : B1, virtual B2 {
void method() const& override {
std::cout << "test1 S";
}
};
}
// Multiple with both virtual inheritance, both polymorphic
namespace test11 {
struct B1 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct B2 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct S : virtual B1, virtual B2 {
void method() const& override {
std::cout << "test1 S";
}
};
}
int main() {
print_pointer(&test1::S::method);
std::cout << '\n';
print_pointer(&test2::S::method);
std::cout << '\n';
print_pointer(&test3::S::method);
std::cout << '\n';
print_pointer(&test4::S::method);
std::cout << '\n';
print_pointer(&test5::S::method);
std::cout << '\n';
print_pointer(&test6::S::method);
print_pointer(&test6::B1::method);
std::cout << '\n';
print_pointer(&test7::S::method);
print_pointer(&test7::B1::method);
print_pointer(&test7::B2::method);
std::cout << '\n';
print_pointer(&test8::S::method);
std::cout << '\n';
print_pointer(&test9::S::method);
print_pointer(&test9::B1::method);
std::cout << '\n';
print_pointer(&test10::S::method);
print_pointer(&test10::B1::method);
std::cout << '\n';
print_pointer(&test11::S::method);
print_pointer(&test11::B1::method);
print_pointer(&test11::B2::method);
}
在我的所有示例中,成員函式指標的最后 8 個位元組是 0000000000000000
這是完整的輸出:
b013400000000000 0000000000000000
f013400000000000 0000000000000000
3014400000000000 0000000000000000
d013400000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
1014400000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
活生生的例子
如何在 GCC 上生成具有非零增量的成員函式指標?
uj5u.com熱心網友回復:
我沒有看過 GCC 代碼,所以我只是在做一些猜測和假設。
delta 用于調整this指標。所以我們必須構建一個案例,其中:
MyClass* pThis = ...;
MemberFunctionPointer mfp = ...;
(pThis->*mfp)(); // must adjust this := pThis delta
為什么this(成員函式內的 this 指標)與pThis? 如果我們從不同的類呼叫成員函式,就會發生這種情況:
struct B1
{
char c;
};
struct B2
{
char d;
void memfun();
};
struct S : B1, B2
{
void direct();
};
當你做類似的事情時
B2 b2;
b2.memfun();
這樣我們就不用調整this指標了,this := &b2。換句話說,B2::memfun期望this指標指向(子)物件B2。
B2型別物件內的子物件S偏移B1。因此,當我們寫
S s;
s.memfun();
編譯器必須有效地調整地址從&s到&s.d- 它應用增量。
我們現在可以構造在成員函式指標中生成增量的示例:
using Smfp = void(S::*)();
Smfp m = &S::memfun; // it's really B2::memfun!
S s;
(s.*m)(); // we're calling B2::memfun, therefore we need to adjust this := &s delta == &s.d
注意我們可以寫
m = &S::direct;
(s.*m)(); // calls S::direct, no adjustment, this := &s
這解釋了為什么我們需要將增量存盤為成員函式指標的一部分。
一個小陷阱是用于成員函式指標的型別別:
using B2mfp = void(B2::*)();
B2mfp x = &B2::memfun; // x has no delta!
B2 b;
(b.*x)(); // no need to adjust, this := &b
的型別&S::memfun實際上是void(B2::*)()因為這就是成員函式繼承的作業方式:查找首先搜索S,然后搜索它的基數。沒有專用的S::memfun(沒有代碼),實際上只有B2::memfun我們也可以通過 name/alias 找到的S::memfun。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/328481.html
