【问题标题】:Print address of virtual member function打印虚拟成员函数的地址
【发布时间】:2010-06-18 08:29:53
【问题描述】:

我正在尝试打印虚拟成员函数的地址。 如果我知道哪个类实现了我可以编写的函数:

print("address: %p", &A::func);

但我想做这样的事情:

A *b = new B();

printf("address: %p", &b->func); 
printf("address: %p", &b->A::func);

但是这不会编译。是否有可能做这样的事情,也许在运行时在 vtable 中查找地址?

【问题讨论】:

    标签: c++ virtual vtable


    【解决方案1】:

    目前在 C++ 中没有标准的方法来执行此操作,尽管信息必须在某处可用。否则,程序怎么会调用函数呢?但是,GCC 提供了一个扩展,允许我们检索虚函数的地址:

    void (A::*mfp)() = &A::func;
    printf("address: %p", (void*)(b->*mfp));
    

    ...假设成员函数具有原型void func()。 当您想要缓存虚函数的地址或在生成的代码中使用它时,这可能非常有用。除非您指定 -Wno-pmf-conversions,否则 GCC 将警告您有关此构造的信息。它不太可能与任何其他编译器一起使用。

    【讨论】:

      【解决方案2】:

      指向成员函数的指针并不总是简单的内存地址。请参阅this article 中的表格,其中显示了不同编译器上成员函数指针的大小 - 有些高达 20 字节。

      正如文章概述的那样,成员函数指针实际上是实现定义的数据块,以帮助通过指针解决调用。您可以存储并调用它们 OK,但是如果您想打印它们,您打印什么?最好将其视为一个字节序列,并通过sizeof 获取其长度。

      【讨论】:

      • 但问题仍然存在:您如何识别通过virtual 调用 :) 调用的函数?
      • 这不是问题的一部分,是吗?但我会回答“通过调用它”:)
      【解决方案3】:

      我找到了一种使用反汇编程序 (https://github.com/vmt/udis86) 的方法。步骤是:

      1. 通过普通 C++ 代码获取指向虚函数的指针

      2. 反汇编该地址处的jmp指令

      3. 从反汇编字符串中解析出真实地址

      我是这样做的:

      // First get the raw pointer to the virtual function
      auto myVirtualFuncPtr = &MyClass::myFunc;
      void* myVirtualFuncPtrRaw = (void*&)myVirtualFuncPtr;
      
      // Resolve the real function!
      void* myFuncPtr = resolveVirtualFunctionAddress(myVirtualFuncPtrRaw);
      
      ...
      
      static void* resolveVirtualFunctionAddress(void* address)
      {
          const int jumpInstructionSize = 5;
      
          static ud_t ud_obj;
          ud_init(&ud_obj);
          ud_set_mode(&ud_obj, sizeof(void*) * 8);
          ud_set_syntax(&ud_obj, UD_SYN_INTEL);
          ud_set_pc(&ud_obj, (uint64_t)address);
          ud_set_input_buffer(&ud_obj, (unsigned uint8_t*)address, jumpInstructionSize);
      
          std::string jmpInstruction = "";
      
          if (ud_disassemble(&ud_obj))
          {
              jmpInstruction += ud_insn_asm(&ud_obj);
          }
      
          // TODO: Implement startsWith and leftTrim yourself
          if (startsWith(jmpInstruction, "jmp "))
          {
              std::string jumpAddressStr = leftTrim(jmpInstruction, "jmp ");
              return hexToPointer(jumpAddressStr);
          }
      
          // If the jmp instruction was not found, then we just return the original address
          return address;
      }
      
      static void* hexToPointer(std::string hexString)
      {
          void* address;
          std::stringstream ss;
      
          ss << std::hex << hexString;
          ss >> address;
      
          return address;
      }
      

      【讨论】:

        【解决方案4】:

        对我来说没有多大意义。如果你有一个正常的功能:

        void f( int n ) {
        }
        

        那么你可以获取它的地址:

        f
        

        但您不能获取函数调用的地址,这似乎是您想要做的。

        【讨论】:

        • @GMan 这就是我想我说的。无论如何,我认为这是不可能的。
        【解决方案5】:

        从我在标准中可以看出,您获得动态绑定的唯一时间是在虚函数调用期间。一旦你调用了一个函数,你就是在执行函数中的语句(即,你不能“中途停止”调用并获取地址。)

        我认为这是不可能的。

        【讨论】:

        • 我想它是不可能移植的,但是知道实现应该可以在运行时检查对象的虚拟表。
        猜你喜欢
        • 2011-03-05
        • 2011-06-27
        • 2012-06-22
        • 1970-01-01
        • 2015-08-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-11
        相关资源
        最近更新 更多