首页 > 解决方案 > 何时在 GCC 的成员函数指针中使用 delta?

问题描述

我正在阅读来自 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 上生成具有非零增量的成员函数指针?

标签: c++gccmember-function-pointers

解决方案


我没有看过 GCC 代码,所以我只是在做一些猜测和假设。

增量用于调整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


推荐阅读