我正在尝试打印虚拟成员函数的地址。如果我知道哪个类实现了我可以编写的函数:
print("address: %p", &A::func);
但我想做这样的事情:
A *b = new B();
printf("address: %p", &b->func);
printf("address: %p", &b->A::func);
但是,这不会编译。是否有可能做这样的事情,也许在运行时在 vtable 中查找地址?
尽管信息必须在某处可用,但目前在 C++ 中没有执行此操作的标准方法。否则,程序怎么会调用函数呢?但是,GCC 提供了一个扩展,允许我们检索虚函数的地址:
void (A::*mfp)() = &A::func;
printf("address: %p", (void*)(b->*mfp));
...假设成员函数具有原型void func()
。当您想要缓存虚函数的地址或在生成的代码中使用它时,这可能非常有用。除非您指定-Wno-pmf-conversions
. 它不太可能与任何其他编译器一起使用。
指向成员函数的指针并不总是简单的内存地址。请参阅本文中的表格,该表格显示了不同编译器上成员函数指针的大小 - 有些高达 20 字节。
正如文章概述的那样,成员函数指针实际上是一个实现定义的数据块,用于帮助通过指针解析调用。您可以存储并调用它们 OK,但是如果您想打印它们,您打印什么?最好将其视为字节序列并通过sizeof
.
我找到了一种使用反汇编程序(https://github.com/vmt/udis86)的方法。步骤是:
通过普通 C++ 代码获取指向虚函数的指针
反汇编该jmp
地址处的指令
从反汇编的字符串中解析出真实地址
这是我的做法:
// 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;
}
从我在标准中可以看出,唯一一次获得动态绑定是在虚函数调用期间。一旦你调用了一个函数,你就在函数内执行语句(即,你不能“中途停止”调用并获取地址。)
我认为这是不可能的。
对我来说没有多大意义。如果你有一个正常的功能:
void f( int n ) {
}
那么你可以获取它的地址:
f
但是您不能获取函数调用的地址,这似乎是您想要做的。