C++ llvm如何知道成员函数指针是否指向虚拟函数?

C++ llvm如何知道成员函数指针是否指向虚拟函数?,c++,llvm,member-function-pointers,C++,Llvm,Member Function Pointers,在编写了一些关于成员函数指针的代码并进行了阅读之后,我已经了解了llvm中成员函数指针的布局 但是让我吃惊的是如何使用成员函数指针获取函数的地址。我无法判断成员函数指针mfptr是指向虚拟成员函数还是指向普通成员函数。我不确定llvm究竟是这样做的,但我发现安腾ABI的普通成员函数和虚拟函数指针之间的区别通常在于记录本身的结构,如下所述: 虚函数指针的形式由 处理器专用C++的ABI实现。具体 64位安腾共享库构建案例,一个虚拟函数 指针项包含一对组件(每个64位):值 目标GP值和实际函数地址

在编写了一些关于成员函数指针的代码并进行了阅读之后,我已经了解了llvm中成员函数指针的布局


但是让我吃惊的是如何使用成员函数指针获取函数的地址。我无法判断成员函数指针mfptr是指向虚拟成员函数还是指向普通成员函数。

我不确定llvm究竟是这样做的,但我发现安腾ABI的普通成员函数和虚拟函数指针之间的区别通常在于记录本身的结构,如下所述:

虚函数指针的形式由 处理器专用C++的ABI实现。具体 64位安腾共享库构建案例,一个虚拟函数 指针项包含一对组件(每个64位):值 目标GP值和实际函数地址的。就是, 而不是一个普通的函数指针,它指向这样一个 双组件描述符,虚拟函数指针项是 描述符

也就是说,普通函数指针是地址,而虚拟函数指针是由全局位置偏移量(GP)和虚拟函数重写的有效地址组成的描述符。现在我想,记录的大小和某种装饰(如果你认为你所指的链接中提到的是“1”)使得区分一种指针和另一种指针成为可能

编辑

在llvm的clang前端的
TargetCXXABI
类的实现中,我发现了关于虚拟函数成员的vtable记录w.r.t.定义的另一个提示。此类公开了一个API(行),该API告知成员函数的主体是否对齐

正如PaulR在其回答中所述,这证实了LSB用于区分正常成员函数和虚拟成员函数的事实。但是这不是因为指针大小和对齐方式,最小可寻址单元总是字节,所以指针可以是奇数-但是因为在In中C++的ABI中,正常成员函数的主体是对齐的,所以它们的地址总是按设计的偶数。 但情况并非总是如此,事实上,在该方法的实现中,提到了一些架构(例如ARM)将鉴别器存储在
指针的调整中,而不是存储在函数地址中

这是一个真正与处理器体系结构有关的特性,除了LS8+64的一般规则(X8664),你应该检查每一个ItA+的C++ abi。p> 您可以在文档中看到:

对于虚拟函数,它是1加上虚拟表偏移量(in 函数的字节数

根据这项规定,它说:

虚拟表中的偏移量由该分配序列和自然ABI大小和对齐方式确定

偏移量必须符合函数指针的对齐要求

对应的C ABI中规定了“POD”型功能指针的对准要求。我假设指针与其大小对齐,因此指针的地址(以及偏移量)必须是偶数,其最低有效位必须为零

因此,实现可以只检查偏移量/指针字段的LSB,并知道当且仅当LSB是一个时,它正在处理一个虚拟方法

一旦在虚拟表中有了偏移量,它就会从对象读取虚拟表指针,并使用成员指针的偏移量从虚拟表加载函数的实际地址

class C {
    virtual int someMethod();
};

int invokeAMethod(C *c, int (C::*method)()) {
    return (c->*method)();
}
在x86_64上,clang确实为方法指针的“ptr”成员的LSB创建了一个检查:

invokeAMethod(C*, int (C::*)()): # @invokeAMethod(C*, int (C::*)())
  // c is in rdi, method.adj is in rdx, and method.ptr is in rdx
  // adjust this pointer
  add rdi, rdx
  // check whether method is virtual
  test sil, 1
  // if it is not, skip the following
  je .LBB0_2
  // load the vtable pointer from the object
  mov rax, qword ptr [rdi]
  // index into the vtable with the corrected offset to load actual method address
  mov rsi, qword ptr [rax + rsi - 1]
.LBB0_2:
  // here the actual address of the method is in rsi, we call it
  // in this particular case we return the same type
  // and do not need to call any destructors
  // so we can tail call
  jmp rsi # TAILCALL

我无法共享此特定示例的godbolt链接,因为我的一个浏览器插件受到干扰,但您可以自己玩类似的示例。

不声称这是LLVM实现,但可能最简单的实现是它没有指向虚拟函数。相反,编译器可能会插入一个未命名的,非虚助手函数,它没有调用虚拟函数的其他目的。我认为1是用来区分Null pTR和指向虚拟表的PTR的函数,它位于VTABLE的第一个元素中。>但是,因为在In的C++中,正常成员函数的体是对齐的,所以它们的地址是总是设计成偶数。关于主体对齐的段落在哪里?