打印虚拟成员函数的地址

40

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

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

但是我想要做类似这样的事情:

A *b = new B();

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

然而,这并不能编译。可能有办法做到这一点吗?也许可以在运行时查找vtable中的地址?

5个回答

20

目前在C++中没有标准的方法来做到这一点,尽管信息必须是可用的。否则,程序如何调用函数呢?然而,GCC提供了一个扩展,允许我们检索虚拟函数的地址:

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

假设成员函数的原型是 void func()。 当你想要缓存虚函数的地址或在生成的代码中使用时,这非常有用。 GCC会警告你这种构造,除非你指定-Wno-pmf-conversions。其他编译器很可能不支持此功能。


6
指向成员函数的指针并不总是简单的内存地址。请参考此文章中列出的表格,其中显示了不同编译器上成员函数指针的大小 - 有些甚至高达20个字节。
正如该文章所概述的那样,成员函数指针实际上是一个实现定义的数据块,用于帮助通过指针解析调用。您可以存储和调用它们,但如果要打印它们,应该打印什么呢?最好将它视为一系列字节,并通过sizeof获取其长度。

6
然而问题仍然存在:如何确定通过虚函数调用的那个函数呢?:) - Matthieu M.
1
那不是问题的一部分,对吧?但我会回答“通过调用它” :) - AshleysBrain

2
我找到了一种使用反汇编器(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;
}

1
据我所知,在标准中,唯一可以获得动态绑定的时候是在虚函数调用期间。一旦你调用了一个函数,你就会执行函数内部的语句(也就是说,你不能“半途而废”并获取地址)。
我认为这是不可能的。

我想这个可能是不可移植的,但是如果了解实现细节,应该可以在运行时检查对象的虚拟表。 - Matthieu M.

0

对我来说没有太多意义。如果您有一个普通函数:

void f( int n ) {
}

然后您可以获取其地址:

f

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


@GMan 这就是我认为我说的话。无论如何,我认为这是不可能的。 - anon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接