使用指针从 VTable 手动调用函数会产生分段错误

Manually calling a function from VTable using pointers creates segmentation fault

手动调用 second() 函数时出现分段错误。我认为问题在于,程序在使用 [=14= 向前移动地址时丢失了处理 first() 函数在构造的 D class 内存中的位置的信息] 在 int (*pfunSecond)(int) = (int (*)(int)) *(((void***)pb)[0] + 1);。到目前为止我还没有运气修复它。

#include <iostream>

class B{
public:
  virtual int __cdecl first()=0;
  virtual int __cdecl second(int)=0;
};

class D: public B{
public:
  virtual int __cdecl first(){return 42;}
  virtual int __cdecl second(int x){return first()+x;}
  
};

void myFunc(B* pb){
  int (*pfunFirst)() = (int (*)()) *(((void***)pb)[0]);
  int (*pfunSecond)(int) = (int (*)(int)) *(((void***)pb)[0] + 1);

  std::cout << "Call first(): " << pfunFirst() << std::endl; 
  std::cout << "Call second(): " << pfunSecond(20) << std::endl; 
}

int main(void){
  myFunc(new D()); 
  return 0;
}

无需仔细观察,您就是通过非成员函数指针调用这些成员函数(虚拟)。正在使用垃圾 this 指针调用这些函数。

您的问题是:

  1. 你对 vtable 所做的一切都是未定义的行为。但我想你知道这一点,并且愿意接受它。

我假设您对虚函数的布局 table 是正确的;缺少特定的编译器我别无选择。一元 * 看起来很奇怪,但我不知道 vtables 在你的系统上是如何布局的。

  1. 您在伪成员函数指针上缺少隐式 this 指针。

  2. 您放弃了调用约定。

所以:

 int (__cdecl *pfunFirst)(B*) = (int (__cdecl *)(B*)) *(((void***)pb)[0]);
 int (__cdecl *pfunSecond)(B*,int) = (int (__cdecl *)(B*,int)) *(((void***)pb)[0] + 1);

然后pfunFirst(b).

但我猜你的 vtable 布局实际上类似于

using cdecl_entry=void(__cdecl*)(void*);

struct vtable_t{
  cdecl_entry functions[1];
};
struct has_vtable{
  vtable_t const* vtable;
};
template<class R,class...Args>using cdecl_func=R(__cdecl *)(Args...);
template<class Base, class R, class...Args>
cdecl_func<R,Base*,Args...> get_vtable_entry(Base const* b, std::size_t n){
  return reinterpret_cast<cdecl_func<R,Base*,Args...>(reinterpret_cast<has_vtable const*>(b)->vtable->functions[n]);
}

这仍然是 UB,但至少不包含 void***s

auto pfunFirst = get_vtable_entry<Base, int>(pb,0);