简体   繁体   English

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

[英]When is the delta used in a member function pointer on GCC?

I was reading the article Member Function Pointers and the Fastest Possible C++ Delegates from Don Clugston and was experimenting with this stuff myself and was not able to reproduce a case correctly.我正在阅读 Don Clugston 的文章Member Function Pointers and the Fastest possible C++ Delegates并且自己正在试验这些东西,但无法正确重现案例。

Of course, the code from Don Clugston is undefined behaviour.当然,Don Clugston 的代码是未定义的行为。

This is specifically about GCC's representation of member function pointers.这特别是关于 GCC 对成员函数指针的表示。

Here's a code snippet from the article about the GCC member function representation (copied as is from the article, not actual code, don't even compile):这是关于 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

Of course, the standard says nothing about this.当然,标准没有说明这一点。 Also, GCC ABI might have changed a lot since the article was written.此外,自本文撰写以来,GCC ABI 可能已经发生了很大变化。 However, I'm not interested in the standard or defined behaviour.但是,我对标准或定义的行为不感兴趣。 I am interested in the current ABI and what the compiler do.我对当前的 ABI 和编译器的作用很感兴趣。

The problem is that I haven't been able to produce a member function pointer that fills the delta value for me to experiment with it.问题是我无法生成一个成员函数指针来填充delta值,以便我对其进行试验。

I assumed something similar to delta still exist since the size of a member function pointer is still the size of two pointer.我假设类似于delta东西仍然存在,因为成员函数指针的大小仍然是两个指针的大小。 Also, according to my observations, the vtable index trick still apply today.另外,根据我的观察,vtable 索引技巧今天仍然适用。

Here's what I tried:这是我尝试过的:

#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);
}

In all of my example, the last 8 byte of the member function pointer is 0000000000000000在我所有的例子中,成员函数指针的最后 8 个字节是0000000000000000

Here's the complete output:这是完整的输出:

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活生生的例子

How can I produce a member function pointer with a non zero delta on GCC?如何在 GCC 上生成具有非零增量的成员函数指针?

I haven't looked at the GCC code, so I'm just doing some guesswork and hypotheses.我没有看过 GCC 代码,所以我只是在做一些猜测和假设。

The delta is used to adjust the this pointer. delta 用于调整this指针。 So we have to construct a case where:所以我们必须构建一个案例,其中:

MyClass* pThis = ...;
MemberFunctionPointer mfp = ...;
(pThis->*mfp)(); // must adjust this := pThis + delta

Why would this (the this pointer inside the member function) be different from pThis ?为什么this (成员函数内的 this 指针)与pThis不同? This can happen if we call a member function from a different class :如果我们从不同的类调用成员函数就会发生这种情况:

struct B1
{
    char c;
};

struct B2
{
    char d;
    void memfun();
};

struct S : B1, B2
{
    void direct();
};

When you do something like当你做类似的事情时

B2 b2;
b2.memfun();

Then we don't have to adjust the this pointer, this := &b2 .然后我们不必调整this指针, this := &b2 In other words, B2::memfun expects the this pointer to point to the (sub)object B2 .换句话说, B2::memfun期望this指针指向(子)对象B2

The subobject B2 inside an object of type S is offset by B1 . S类型对象内的子对象B2B1偏移。 Therefore, when we write因此,当我们写

S s;
s.memfun();

the compiler has to adjust the address from &s to &s.d effectively - it applies a delta.编译器必须有效地将地址从&s调整为&s.d - 它应用增量。


We can now construct the example that generates a delta in the member function pointer:我们现在可以构造在成员函数指针中生成增量的示例:

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

Note that we can write注意我们可以写

m = &S::direct;
(s.*m)(); // calls S::direct, no adjustment, this := &s

this explains why we need to store the delta as part of the member function pointer.这解释了为什么我们需要将增量存储为成员函数指针的一部分。


A slight pitfall is the class type used for the member function pointer:一个小陷阱是用于成员函数指针的类类型:

using B2mfp = void(B2::*)();
B2mfp x = &B2::memfun; // x has no delta!

B2 b;
(b.*x)(); // no need to adjust, this := &b

The type of &S::memfun is actually void(B2::*)() because that's how inheritance of member functions works: the lookup first searches S and then its bases. &S::memfun的类型实际上是void(B2::*)()因为这就是成员函数的继承方式:查找首先搜索S然后搜索它的基数。 There's no dedicated S::memfun (no code for it), there's really only B2::memfun which we can also find with the name/alias S::memfun .没有专用的S::memfun (没有代码),实际上只有B2::memfun ,我们也可以通过名称/别名S::memfun找到S::memfun

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM