- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
我知道普通函数指针包含要指向的函数的起始地址,因此当使用普通函数指针时,我们只需跳转到存储的地址即可。但是指向对象成员函数的指针包含什么?
考虑:
class A
{
public:
int func1(int v) {
std::cout << "fun1";
return v;
}
virtual int func2(int v) {
std::cout << "fun2";
return v;
}
};
int main(int argc, char** argv)
{
A a;
int (A::*pf)(int a) = argc > 2 ? &A::func1 : &A::func2;
static_assert(sizeof(pf) == (sizeof(void*), "Unexpected function size");
return (a.*pf)(argc);
}
this
作为第一个参数实现为普通函数)中获取其值。
最佳答案
当然,这取决于编译器和目标体系结构,并且有多种方法可以实现。但是,我将描述它如何在我最常使用的系统上(在Linux x86_64上使用g++)工作。
g++遵循Itanium C++ ABI,它描述了许多架构可以在幕后实现各种C++功能(包括虚拟功能)的一种方式的许多细节。
ABI在第2.3节中说了关于成员函数的指针:
A pointer to member function is a pair as follows:
ptr:
For a non-virtual function, this field is a simple function pointer. ... For a virtual function, it is 1 plus the virtual table offset (in bytes) of the function, represented as a
ptrdiff_t
. The value zero represents a NULL pointer, independent of the adjustment field value below.adj:
The required adjustment to
this
, represented as aptrdiff_t
.It has the size, data size, and alignment of a class containing those two members, in that order.
A
的vtable / vptr设置将类似于以下C代码:
struct A__virt_funcs {
int (*func2)(A*, int);
};
struct A__vtable {
ptrdiff_t offset_to_top;
const std__typeinfo* typeinfo;
struct A__virt_funcs funcs;
};
struct A {
const struct A__virt_funcs* vptr;
};
int A__func1(struct A*, int v) {
std__operator__ltlt(&std__cout, "fun1");
return v;
}
int A__func2(struct A*, int v) {
std__operator__ltlt(&std__cout, "fun2");
return v;
}
extern const std__typeinfo A__typeinfo;
const struct A__vtable vt_for_A = { 0, &A__typeinfo, { &A__func2 } };
void A__initialize(A* a) {
a->vptr = &vt_for_A.funcs;
}
(是的,实名处理方案需要对函数参数类型进行某些操作以允许重载,并且还需要做更多的事情,因为涉及的
operator<<
实际上是函数模板的一种特殊化。但这不重要。)
main()
(带有
-O0 -fno-stack-protector
选项)获得的程序集。我的评论已添加。
Dump of assembler code for function main:
// Standard stack adjustment for function setup.
0x00000000004007e6 <+0>: push %rbp
0x00000000004007e7 <+1>: mov %rsp,%rbp
0x00000000004007ea <+4>: push %rbx
0x00000000004007eb <+5>: sub $0x38,%rsp
// Put argc in the stack at %rbp-0x34.
0x00000000004007ef <+9>: mov %edi,-0x34(%rbp)
// Put argv in the stack at %rbp-0x40.
0x00000000004007f2 <+12>: mov %rsi,-0x40(%rbp)
// Construct "a" on the stack at %rbp-0x20.
// 0x4009c0 is &vt_for_A.funcs.
0x00000000004007f6 <+16>: mov $0x4009c0,%esi
0x00000000004007fb <+21>: mov %rsi,-0x20(%rbp)
// Check if argc is more than 2.
// In both cases, "pf" will be on the stack at %rbp-0x30.
0x00000000004007ff <+25>: cmpl $0x2,-0x34(%rbp)
0x0000000000400803 <+29>: jle 0x400819 <main+51>
// if (argc <= 2) {
// Initialize pf to { &A__func2, 0 }.
0x0000000000400805 <+31>: mov $0x4008ce,%ecx
0x000000000040080a <+36>: mov $0x0,%ebx
0x000000000040080f <+41>: mov %rcx,-0x30(%rbp)
0x0000000000400813 <+45>: mov %rbx,-0x28(%rbp)
0x0000000000400817 <+49>: jmp 0x40082b <main+69>
// } else { [argc > 2]
// Initialize pf to { 1, 0 }.
0x0000000000400819 <+51>: mov $0x1,%eax
0x000000000040081e <+56>: mov $0x0,%edx
0x0000000000400823 <+61>: mov %rax,-0x30(%rbp)
0x0000000000400827 <+65>: mov %rdx,-0x28(%rbp)
// }
// Test whether pf.ptr is even or odd:
0x000000000040082b <+69>: mov -0x30(%rbp),%rax
0x000000000040082f <+73>: and $0x1,%eax
0x0000000000400832 <+76>: test %rax,%rax
0x0000000000400835 <+79>: jne 0x40083d <main+87>
// int (*funcaddr)(A*, int); [will be in %rax]
// if (is_even(pf.ptr)) {
// Just do:
// funcaddr = pf.ptr;
0x0000000000400837 <+81>: mov -0x30(%rbp),%rax
0x000000000040083b <+85>: jmp 0x40085c <main+118>
// } else { [is_odd(pf.ptr)]
// Compute A* a2 = (A*)((char*)&a + pf.adj); [in %rax]
0x000000000040083d <+87>: mov -0x28(%rbp),%rax
0x0000000000400841 <+91>: mov %rax,%rdx
0x0000000000400844 <+94>: lea -0x20(%rbp),%rax
0x0000000000400848 <+98>: add %rdx,%rax
// Compute funcaddr =
// (int(*)(A*,int)) (((char*)(a2->vptr))[pf.ptr-1]);
0x000000000040084b <+101>: mov (%rax),%rax
0x000000000040084e <+104>: mov -0x30(%rbp),%rdx
0x0000000000400852 <+108>: sub $0x1,%rdx
0x0000000000400856 <+112>: add %rdx,%rax
0x0000000000400859 <+115>: mov (%rax),%rax
// }
// Compute A* a3 = (A*)((char*)&a + pf.adj); [in %rcx]
0x000000000040085c <+118>: mov -0x28(%rbp),%rdx
0x0000000000400860 <+122>: mov %rdx,%rcx
0x0000000000400863 <+125>: lea -0x20(%rbp),%rdx
0x0000000000400867 <+129>: add %rdx,%rcx
// Call int r = (*funcaddr)(a3, argc);
0x000000000040086a <+132>: mov -0x34(%rbp),%edx
0x000000000040086d <+135>: mov %edx,%esi
0x000000000040086f <+137>: mov %rcx,%rdi
0x0000000000400872 <+140>: callq *%rax
// Standard stack cleanup for function exit.
0x0000000000400874 <+142>: add $0x38,%rsp
0x0000000000400878 <+146>: pop %rbx
0x0000000000400879 <+147>: pop %rbp
// Return r.
0x000000000040087a <+148>: retq
End of assembler dump.
但是,成员函数指针的
adj
值如何处理?程序集在执行vtable查找之前以及在调用函数之前将其添加到
a
的地址中,无论该函数是否是虚拟的。但是
main
中的两种情况都将其设置为零,因此我们还没有真正看到它在起作用。
adj
值。现在假设我们有:
class B
{
public:
virtual void func3() {}
int n;
};
class C : public B, public A
{
public:
int func4(int v) { return v; }
int func2(int v) override { return v; }
};
类型为
C
的对象的布局包含
B
子对象(包含另一个vptr和
int
),然后是
A
子对象。因此,
A
中包含的
C
的地址与
C
本身的地址不同。
C*
指针转换为
A*
指针,C++编译器通过在地址值中添加正确的偏移量来解决这一差异。 C++还允许将
A
成员函数的指针转换为
C
成员函数的指针(因为
A
的任何成员也是
C
的成员),并且在发生这种情况时(对于非null成员函数指针),需要进行类似的偏移量调整。因此,如果我们有:
int (A::*pf1)(int) = &A::func1;
int (C::*pf2)(int) = pf1;
在幕后成员函数指针中的值将是
pf1 = { &A__func1, 0 };
和
pf2 = { &A__func1, offset_A_in_C };
。
C c;
int n = (c.*pf2)(3);
编译器将通过向地址
pf2.adj
中添加偏移量
&c
来找到隐式的“this”参数,从而实现对成员函数指针的调用,这是很好的,因为这将是
A*
期望的有效
A__func1
值。
func2
将使用
A*
“this”参数进行调用,因为这是原始覆盖声明的地方是,并且编译器通常将无法知道“this”参数是否实际上是任何其他类型。但是覆盖
C::func2
的定义需要
C*
“this”参数。因此,当最派生的类型是
C
时,
A
子对象内的vptr将指向一个vtable,该表的入口不指向
C::func2
本身的代码,而是一个微小的“thunk”函数,该函数除了从odt中减去
offset_A_in_C
外什么也不做“this”参数,然后将控制权传递给实际的
C::func2
。
关于c++ - 指向成员函数的指针如何工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49288989/
我在Windows 10中使用一些简单的Powershell代码遇到了这个奇怪的问题,我认为这可能是我做错了,但我不是Powershell的天才。 我有这个: $ix = [System.Net.Dn
var urlsearch = "http://192.168.10.113:8080/collective-intellegence/StoreClicks?userid=" + userId +
我有一个非常奇怪的问题,过去两天一直让我抓狂。 我有一个我试图控制的串行设备(LS 100 光度计)。使用设置了正确参数的终端(白蚁),我可以发送命令(“MES”),然后是定界符(CR LF),然后我
我目前正试图让无需注册的 COM 使用 Excel 作为客户端,使用 .NET dll 作为服务器。目前,我只是试图让概念验证工作,但遇到了麻烦。 显然,当我使用 Excel 时,我不能简单地使用与可
我开发了简单的 REST API - https://github.com/pavelpetrcz/MandaysFigu - 我的问题是在本地主机上,WildFly 16 服务器的应用程序运行正常。
我遇到了奇怪的情况 - 从 Django shell 创建一些 Mongoengine 对象是成功的,但是从 Django View 创建相同的对象看起来成功,但 MongoDB 中没有出现任何数据。
我是 flask 的新手,只编写了一个相当简单的网络应用程序——没有数据库,只是一个航类搜索 API 的前端。一切正常,但为了提高我的技能,我正在尝试使用应用程序工厂和蓝图重构我的代码。让它与 pus
我的谷歌分析 JavaScript 事件在开发者控制台中运行得很好。 但是当从外部 js 文件包含在页面上时,它们根本不起作用。由于某种原因。 例如; 下面的内容将在包含在控制台中时运行。但当包含在单
这是一本名为“Node.js 8 the Right Way”的书中的任务。你可以在下面看到它: 这是我的解决方案: 'use strict'; const zmq = require('zeromq
我正在阅读文本行,并创建其独特单词的列表(在将它们小写之后)。我可以使它与 flatMap 一起工作,但不能使它与 map 的“子”流一起工作。 flatMap 看起来更简洁和“更好”,但为什么 di
我正在编写一些 PowerShell 脚本来进行一些构建自动化。我发现 here echo $? 根据前面的语句返回真或假。我刚刚发现 echo 是 Write-Output 的别名。 写主机 $?
关闭。这个问题不满足Stack Overflow guidelines .它目前不接受答案。 想改善这个问题吗?更新问题,使其成为 on-topic对于堆栈溢出。 4年前关闭。 Improve thi
我将一个工作 View Controller 类从另一个项目复制到一个新项目中。我无法在新项目中加载 View 。在旧项目中我使用了presentModalViewController。在新版本中,我
我对 javascript 很陌生,所以很难看出我哪里出错了。由于某种原因,我的功能无法正常工作。任何帮助,将不胜感激。我尝试在外部 js 文件、头部/主体中使用它们,但似乎没有任何效果。错误要么出在
我正在尝试学习Flutter中的复选框。 问题是,当我想在Scaffold(body :)中使用复选框时,它正在工作。但我想在不同的地方使用它,例如ListView中的项目。 return Cente
我们当前使用的是 sleuth 2.2.3.RELEASE,我们看不到在 http header 中传递的 userId 字段没有传播。下面是我们的代码。 BaggageField REQUEST_I
我有一个组合框,其中包含一个项目,比如“a”。我想调用该组合框的 Action 监听器,仅在手动选择项目“a”完成时才调用。我也尝试过 ItemStateChanged,但它的工作原理与 Action
你能看一下照片吗?现在,一步前我执行了 this.interrupt()。您可以看到 this.isInterrupted() 为 false。我仔细观察——“这个”没有改变。它具有相同的 ID (1
我们当前使用的是 sleuth 2.2.3.RELEASE,我们看不到在 http header 中传递的 userId 字段没有传播。下面是我们的代码。 BaggageField REQUEST_I
我正在尝试在我的网站上设置一个联系表单,当有人点击发送时,就会运行一个作业,并在该作业中向所有管理员用户发送通知。不过,我在失败的工作表中不断收到此错误: Illuminate\Database\El
我是一名优秀的程序员,十分优秀!