下面先来看c++的源码:

#include <cstdio>
using namespace std; class X {
public:
int get1() {
return ;
}
virtual int get2() {
return ;
}
virtual int get3() {
return ;
}
}; int main() {
X x;
X* xp = &x;
int(X::*gp1)() = &X::get1;
int(X::*gp2)() = &X::get2;
int(X::*gp3)() = &X::get3;
/*********************输出各个成员函数指针的值*****************/
printf("gp1 = %lu\n", gp1);
printf("gp2 = %lu\n", gp2);
printf("gp3 = %lu\n", gp3); /********************用成员函数指针调用虚函数*******************/
(x.*gp1)();
(xp->*gp1)();
(x.*gp2)();
(xp->*gp2)();
(x.*gp3)();
(xp->*gp3)();
}

类X有3个成员函数,其中get1是普通的成员函数,而get2和get3都分别是虚成员函数。在main函数里面分别定义了指向这三个成员函数的指针,并且将他们的值输出来。然后用成员指针对他们进行了调用。下面来看调用后的结果:

可以看到,gp1 gp2 gp3输出的好像都是地址。下面主要来看一下面函数里面,定义三个成员函数指针的汇编码:

; 20   :     int(X::*gp1)() = &X::get1;

    mov    DWORD PTR _gp1$[ebp], OFFSET ?get1@X@@QAEHXZ ; X::get1;取?get1@X@@QAEHXZ所代表的内存地址,即X::get1的地址给gp1

; 21   :     int(X::*gp2)() = &X::get2;

    mov    DWORD PTR _gp2$[ebp], OFFSET ??_9X@@$BA@AE ; X::`vcall'{0}'; 取??_9X@@$BA@AE所代表的内存地址,即X::`vcall'{0}'的地址给gp2

; 22   :     int(X::*gp3)() = &X::get3;

    mov    DWORD PTR _gp3$[ebp], OFFSET ??_9X@@$B3AE ; X::`vcall'{4}';取??_9X@@$B3AE所代表的内存地址,即X::`vcall'{4}'的地址给gp3

通过汇编码可以看到,gp1存储的确实是成员函数get1的地址,而gp2和gp3存储的确不是虚函数get2和get3的地址,而是X::vcall{0}和X::vcall{4}的地址。那么,X::vcall{0}和vcall{4}到底是什么呢?我们继续看汇编代码,接下来是X::vcall{0}的汇编码:

??_9X@@$BA@AE PROC                    ; X::`vcall'{0}', COMDAT
mov eax, DWORD PTR [ecx];寄存是ecx里面保存的是对象x的首地址,
;因此,这里是将对象x首地址处内存内容(即vftable首地址)给寄存器eax
jmp DWORD PTR [eax];跳转到vftable首地址处内存(里面存的是虚函数get2的地址)所存储的地址处执行
;这里就是跳转去执行虚函数get2
??_9X@@$BA@AE ENDP ; X::`vcall'{0}'

通过汇编码,我们可以发现,X::vcall{0}是一段代码,它的作用是跳转到相应的虚函数地址去执行。

下面是X::vcall{4}的汇编码:

??_9X@@$B3AE PROC                    ; X::`vcall'{4}', COMDAT
mov eax, DWORD PTR [ecx];寄存器ecx里面存储的是对象x的首地址
;因此,这里是将对象x首地址处内存内容(即vftable首地址)给寄存器eax
jmp DWORD PTR [eax+];跳转到偏移vftable首地址处4byte处内存(里面存的是虚函数get2的地址)所存储的地址处执行
;这里就是跳转去执行虚函数get3
??_9X@@$B3AE ENDP ; X::`vcall'{4}'

通过汇编码,我们返现,X::vcall{4}和X::vcall{0}的作用一样。

因此,gp2和gp3存储的不是虚函数get2和虚函数get3的地址,而是相应的vcall函数的地址。那么,通过这些成员函数的指针调用函数的时候,有发生了什么?

下面是调用函数的汇编代码:

; 28   :     /********************用成员函数指针调用虚函数*******************/
; 29 : (x.*gp1)(); lea ecx, DWORD PTR _x$[ebp];取对象x的首地址,给寄存器ecx,作为遗憾参数传递给get1
call DWORD PTR _gp1$[ebp];gp1中存有get1的地址,这里直接调用get1函数 ; 30 : (xp->*gp1)(); mov ecx, DWORD PTR _xp$[ebp];指针变量xp保存有对象x的首地址,这里将对象首地址给寄存器ecx 作为隐含参数传递给get1
call DWORD PTR _gp1$[ebp];gp1中存有get1的地址,这里直接调用get1函数
;用指针和对象操作成员变量指针效果一样 ; 31 : (x.*gp2)(); lea ecx, DWORD PTR _x$[ebp];取对象x的首地址,给寄存器ecx,作为隐含参数传递给X::vcall{0}
call DWORD PTR _gp2$[ebp];gp2中存有X::vcall{0}的地址,这里调用X::vcall{0},由vcall{0}查询虚表,执行get2 ; 32 : (xp->*gp2)(); mov ecx, DWORD PTR _xp$[ebp];用指针调用和用对象x调用效果一样
call DWORD PTR _gp2$[ebp] ; 33 : (x.*gp3)(); lea ecx, DWORD PTR _x$[ebp];将对象x首地址给寄存器ecx,作为隐含参数传递给X::vcall{4}
call DWORD PTR _gp3$[ebp];gp3中存有X::vcall{4}的地址,这里调用X::vcall{4},由vcall{4}查询虚表,调用get3 ; 34 : (xp->*gp3)(); mov ecx, DWORD PTR _xp$[ebp];指针调用和对象调用效果一样
call DWORD PTR _gp3$[ebp]

通过汇编码发现,普通成员函数时通过地址直接调用,而虚成员函数时通先调用vcall函数,然后由vcall函数查询虚表调用相应的虚函数

由此可以看出,一个类里面的每一给虚函数都有一个vcall函数与之对应,通过vcall函数来调用相应的虚函数。

从汇编看c++中成员函数指针(一)的更多相关文章

  1. 为什么 C++ 中成员函数指针是 16 字节?

    当我们讨论指针时,通常假设它是一种可以用 void * 指针来表示的东西,在 x86_64 平台下是 8 个字节大小.例如,下面是来自 维基百科中关于 x86_64 的文章 的摘录: Pushes a ...

  2. 从汇编看c++成员函数指针(三)

    前面的从汇编看c++中成员函数指针(一)和从汇编看c++成员函数指针(二)讨论的要么是单一类,要么是普通的多重继承,没有讨论虚拟继承,下面就来看一看,当引入虚拟继承之后,成员函数指针会有什么变化. 下 ...

  3. 从汇编看c++成员函数指针(二)

    下面先看一段c++源码: #include <cstdio> using namespace std; class X { public: virtual int get1() { ; } ...

  4. 成员函数指针与高效C++委托 (delegate)

    下载实例源代码 - 18.5 Kb 下载开发包库文件 - 18.6 Kb 概要 很遗憾, C++ 标准中没能提供面向对象的函数指针. 面向对象的函数指针也被称为闭包(closures) 或委托(del ...

  5. 成员函数指针与高性能C++委托

    1 引子 标准C++中没有真正的面向对象的函数指针.这一点对C++来说是不幸的,因为面向对象的指针(也叫做“闭包(closure)”或“委托(delegate)”)在一些语言中已经证明了它宝贵的价值. ...

  6. [转]成员函数指针与高性能的C++委托

    原文(作者:Don Clugston):Member Function Pointers and the Fastest Possible C++ Delegates 译文(作者:周翔): 成员函数指 ...

  7. 从汇编看c++中指向成员变量的指针(二)

    在从汇编看c++中指向成员变量的指针(一)中讨论的情形没有虚拟继承,下面来看看,当加入了虚拟继承的时候,指向成员变量的指针有什么变化. 下面是c++源码: #include <iostream& ...

  8. 函数指针和成员函数指针有什么不同,反汇编带看清成员函数指针的本尊(gcc@x64平台)

    函数指针是什么,可能会答指向函数的指针. 成员函数指针是什么,答指向成员函数的指针. 成员函数指针和函数指针有什么不同? 虚函数指针和非虚成员函数指针有什么不同? 你真正了解成员函数指针了吗? 本篇带 ...

  9. 关于C++中的非静态类成员函数指针

    昨天发现了一个问题,就是使用对类中的非静态成员函数使用std::bind时,不能像普通函数一样直接传递函数名,而是必须显式地调用&(取地址),于是引申出我们今天的问题:非静态类成员函数指针和普 ...

随机推荐

  1. 值栈和OGNL 之 7.1 值栈

    7.1  值栈 7.1.1  值栈是什么 简单的说:值栈是对应每一个请求对象的轻量级的内存数据中心. Struts2中一个很激动人心的特性就是引入了值栈,在这里统一管理着数据,供Action.Resu ...

  2. 南阳oj-喷水装置(一)

    喷水装置(一) 时间限制:3000 ms  |  内存限制:65535 KB 难度:3   描述 现有一块草坪,长为20米,宽为2米,要在横中心线上放置半径为Ri的喷水装置,每个喷水装置的效果都会让以 ...

  3. MYSQL开发技巧之行转列和列转行

    行转列--两种方法第一种方法:行转列我们通常是使用交叉连接和子查询的方式做到,比如下面的例子,查询每个name的对应id的和mysql> select * from user; +----+-- ...

  4. 从Ecipse中导出程序至apk

    若未有数字证书: 1. 2. 3. 4. 5. 若已有数字证书: 上面的后3步改为

  5. [C++程序设计]字符数组的赋值与引用

    只能对字符数组的元素赋值,而不能用赋值语句对整个数组赋值. char c[5]; c={′C′,′h′,′i′,′n′,′a′}; //错误,不能对整个数组一次赋值 c[0]=′C′; c[1]=′h ...

  6. [转]如何正确设置nginx中remote_addr和x_forwarded_for参数

    做网站时经常会用到remote_addr和x_forwarded_for这两个头信息来获取客户端的IP,然而当有反向代理或者CDN的情况下,这两个值就不够准确了,需要调整一些配置. 什么是remote ...

  7. C语言中的宏定义

    目录(?)[-] 简单宏定义 带参数的宏 运算符 运算符 宏的通用属性 宏定义中圆括号 创建较长的宏 较长的宏中的逗号运算符 宏定义中的do-while循环do 空操作的定义 预定义宏 C语言中常用的 ...

  8. STL中map与hash_map容器的选择收藏

    这篇文章来自我今天碰到的一个问题,一个朋友问我使用map和hash_map的效率问题,虽然我也了解一些,但是我不敢直接告诉朋友,因为我怕我说错了,通过我查询一些帖子,我这里做一个总结!内容分别来自al ...

  9. 借贷宝注册送现金疯转 新闻PS图背后真相

    动动手指,20元人民币立即到手:http://www.cnblogs.com/mfryf/p/4754384.html 近日,九鼎投资旗下投资平台借贷宝开展的“拉上好友抢红包,轻轻松松玩出钱”引起市场 ...

  10. ubuntu 文件readonly的问题: W10: Warning: Changing a readonly file 解决办法

    日前,笔者遇到一个奇怪且让人蛋疼的问题,借用别人的话"大家在linux上编辑文件的时候,明明是使用的root登录的,可是这种至高无上的权限在按下i的时候被那串红色错误亵渎了W10: Warn ...