gpt4 book ai didi

c++ - 什么时候在 GCC 上的成员函数指针中使用增量?

转载 作者:行者123 更新时间:2023-12-04 13:24:11 24 4
gpt4 key购买 nike

我正在阅读文章Member Function Pointers and the Fastest Possible C++ Delegates来自 Don Clugston 并且自己正在试验这些东西,但无法正确复制案例。
当然,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
Live example
如何在 GCC 上生成具有非零增量的成员函数指针?

最佳答案

我没有看过 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 .
子对象 B2S 类型的对象内被 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我们也可以通过名称/别名 S::memfun 找到它.

关于c++ - 什么时候在 GCC 上的成员函数指针中使用增量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69647229/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com