linux 虚函数调用性能,C++对象布局及多态实现探索之虚函数调用

我们再看看虚成员函数的调用。类C041中含有虚成员函数,它的定义如下:

struct C041

{

C041() : c_(0x01) {}

virtual void foo() { c_ = 0x02; }

char c_;

};

执行如下代码:

C041 obj;

PRINT_DETAIL(C041, obj)

PRINT_VTABLE_ITEM(obj, 0, 0)

obj.foo();

C041 * pt = &obj;

pt->foo();

结果如下:

The detail of C041 is 14 b3 45 00 01

obj : objadr:0012F824 vpadr:0012F824 vtadr:0045B314 vtival(0):0041DF1E

我们打印出了C041的对象内存布局及它的虚表信息。

先看看obj.foo();的汇编代码:

004230DF lea ecx,[ebp+FFFFF948h]

004230E5 call 0041DF1E

和前一篇文章中看过的普通的成员函数调用产生的汇编代码一样。这说明了通过对象进行函数调用,即使被调用的函数是虚函数也是静态绑定,即在编译时决议出函数的地址。不会有多态的行为发生。

我们跟踪进去看看函数的汇编代码。

01 004263F0 push ebp

02 004263F1 mov ebp,esp

03 004263F3 sub esp,0CCh

04 004263F9 push ebx

05 004263FA push esi

06 004263FB push edi

07 004263FC push ecx

08 004263FD lea edi,[ebp+FFFFFF34h]

09 00426403 mov ecx,33h

10 00426408 mov eax,0CCCCCCCCh

11 0042640D rep stos dword ptr [edi]

12 0042640F pop ecx

13 00426410 mov dword ptr [ebp-8],ecx

14 00426413 mov eax,dword ptr [ebp-8]

15 00426416 mov byte ptr [eax+4],2

16 0042641A pop edi

17 0042641B pop esi

18 0042641C pop ebx

19 0042641D mov esp,ebp

20 0042641F pop ebp

21 00426420 ret

值得注意的是第14、15行。第14行把this指针的值移到eax寄存器中,第15行给类的第一个成员变量赋值,这时我们可以看到在取变量的地址时用的是[eax+4],即跳过了对象布局最前面的4字节的虚表指针。

接下来我们看看通过指针进行的虚函数调用pt->foo();,产生的汇编代码如下:

01 004230F6 mov eax,dword ptr [ebp+FFFFF900h]

02 004230FC mov edx,dword ptr [eax]

03 004230FE mov esi,esp

04 00423100 mov ecx,dword ptr [ebp+FFFFF900h]

05 00423106 call dword ptr [edx]

第1行把pt指向的地址移入eax寄存器,这样eax中就保存了对象的内存地址,同时也是类的虚表指针的地址。第2行取eax中指针指向的值(注意不是 eax的值)到edx寄存器中,实际上也就是虚表的地址。执行完这两条指令后,我们看看eax和edx中的值,果然和我们前面打印的obj的虚表信息中的 vpadr和vtadr的值是一样的,分别为0x0012F824和0x0045B314。第4行同样用ecx寄存器来保存并传递对象地址,即 this指针的值。第5行的call指令,我们可以看到目的地址不象通过对象来调用那样,是一个直接的函数地址。而是将edx中的值做为指针来进行间接调用。前面我们已经知道edx中存放的实际是虚表的地址,我们也知道虚表实际是一个指针数组。这样第5行的调用实际就是取到虚表中的第一个条目的值,即 C041::foo()函数的地址。如果被调用的虚函数对应的虚表条目的索引不是0,将会看到edx后加上一个索引号乘4后的偏移值。继承跟踪可以发现, ptr[edx]的值为0x0041DF1E,也和我们打印的vtival(0)的值相同。前面已经提到过,这个地址实际也不是真正的函数地址,是一个跳转指令,继续执行就到了真正的函数代码部分(即前面列出的代码)。

我们在上面看到的这个过程,就是动态绑定的过程。因为我们是通过指针来调用虚成员函数,所以会产生动态绑定,即使指针的类型和对象的类型是一样的。为了保证多态的语义,编译器在产生call指令时,不象静态绑定时那样,是在编译时决议出一个确定的地址值。相反它是通过用发出调用的指针指向的对象中的虚指针,来迂回的找到对象所对应类型的虚表,及虚表中相应条目中存放的函数地址。这样具体调用哪个函数就与指针的类型是无关的,只与具体的对象相关,因为虚指针是存放在具体的对象中,而虚表只和对象的类型相关。这也就是多态会发生的原因。

请回忆一下前面讨论过的C071类,当子类重写从父类继承的虚函数时,子类的虚表内容的变化,及和父类虚表内容的区别(请参照第二篇中打印的子类和父类的虚表信息)。具体的通过指向子类对象的父类指针来调用被子类重写过的虚函数时的调用过程,请有兴趣的朋友自己调试一下,这里不再列出。

另外前面在《C++对象布局及多态实现之动态和强制转换》中我们讨论了指针的类型动态转换。我们在这里再利用C041、C042及C051类,来看看指针的类型动态转换。这几个类的定义请参见前文。类C051从C041和C042多重继承而来,且后两个类都有虚函数。执行如下代码:

C051 obj;

C041 * pt1 = dynamic_cast(&obj);

C042 * pt2 = dynamic_cast(&obj);

pt1->foo();

pt2->foo2();

第一个动态转型对应的汇编代码为:

00404B59 lea eax,[ebp+FFFFF8ECh]

00404B5F mov dword ptr [ebp+FFFFF8E0h],eax

因为不需要调整指针位置,所以很直接,取出对象的地址后直接赋给了指针。

第二个动态转型牵涉到了指针位置的调整,我们来看看它的汇编代码:

01 00404B65 lea eax,[ebp+FFFFF8ECh]

02 00404B6B test eax,eax

03 00404B6D je 00404B7D

04 00404B6F lea ecx,[ebp+FFFFF8F1h]

05 00404B75 mov dword ptr [ebp+FFFFF04Ch],ecx

06 00404B7B jmp 00404B87

07 00404B7D mov dword ptr [ebp+FFFFF04Ch],0

08 00404B87 mov edx,dword ptr [ebp+FFFFF04Ch]

09 00404B8D mov dword ptr [ebp+FFFFF8D4h],edx

代码要复杂的多。&obj运算后得到的是一个指针,前三行指令就是判断这个指针是否为NULL。奇怪的是第4行并没有根据eax中的地址(即对象的起始地址)来进行指针的位置调整,而是直接把[ebp+FFFFF8F1h]的地址取到ecx寄存器中。第1行指令中的[ebp+ FFFFF8ECh]实际是得到对象的地址,ebp所加的那个数实际是个负数(补码)也就是对象的偏移地址。对比两个数发现相差5字节,这样实际上第4行是直接得到了指针调整后的地址,即将指针指向了对象中的属于C042的部分。后面的代码又通过一个临时变量及edx寄存器把调整后的指针值最终存到了 pt2指针中。

这些代码实际可以优化成二行:

lea eax, [ebp+FFFFF8F1h]

mov dword ptr [ebp+FFFFF8d4h], eax

我们曾提到C051类有两个虚表,相应对象中有也两个虚表指针,之所以不合并为一个,就是为了处理指针的类型动态转换。结合前面对于多态的讨论,我们就可以理解得更清楚了。pt2->foo2();调用时,对象的类型还是C051,但经过指针动态转换pt2指向了对象中属于C042的部分的起始,也就是第二个虚表指针。这样在进行函数调用时就不需要再做额外的处理了。我们看看pt1->foo();及pt2->foo2 ();产生的汇编码即知。

01 00404B93 mov eax,dword ptr [ebp+FFFFF8E0h]

02 00404B99 mov edx,dword ptr [eax]

03 00404B9B mov esi,esp

04 00404B9D mov ecx,dword ptr [ebp+FFFFF8E0h]

05 00404BA3 call dword ptr [edx]

06 00404BA5 cmp esi,esp

07 00404BA7 call 0041DDDE

08 00404BAC mov eax,dword ptr [ebp+FFFFF8D4h]

09 00404BB2 mov edx,dword ptr [eax]

10 00404BB4 mov esi,esp

11 00404BB6 mov ecx,dword ptr [ebp+FFFFF8D4h]

12 00404BBC call dword ptr [edx]

13 00404BBE cmp esi,esp

14 00404BC0 call 0041DDDE

前7行为pt1->foo();,后7行为pt2->foo2();。唯一不同的是指针指向的地址不同,调用机制是一样的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445744.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

netflow流量分析工具 linux,Centos5/Linux安装Nfdump和Nfsen图形界面分析netflow数据

Nfdump是linux下netflow数据采集分析工具,Nfsen是基于nfdump是web界面工具,服务器需先安装web服务器和php环境。安装rrdtool及所需组件:yum install perl-rrdtool rrdtool rrdtool-devel rrdutils flex byacc安装所需perl模块:yum…

linux嵌入式平台测试,protobuf-c 在arm linux 嵌入式平台的使用 测试

关于什么是protobuf,网上搜搜一大堆,很多人用的都还是json,以为json是多种语言传输数据是万能的,看完了protobuf的实现,就明白了简单高效才是王道。1、首先写一个.proto扩展名的文件json.proto,内容格式如下…

Linux gitpush错误,linux – GIT:无法推送(奇怪的配置问题)

我正在全新安装Linux Mint.尝试从任何存储库推送时,我收到以下错误:error: Malformed value for push.default: simpleerror: Must be one of nothing,matching,tracking or current.fatal: bad config file line 8 in /home/leng/.gitconfigfatal: Could not read …

linux+shell+func,Linux shell编程笔记总结

Linux Shell学习笔记简介Linux系统的shell作为操作系统的外壳,为用户提供使用操作系统的接口。它是命令语言、命令解释程序及程序设计语言的统称。shell是用户和Linux内核之间的接口程序,如果把Linux内核想象成一个球体的中心,shell就是围绕内…

linux版车机安装步骤,RedHat Linux 9.0的安装(详细图解安装过程)

RedHat Linux版本:" b, t) b) b# }, t# z- fC& S$ x0 }) GRedHat Linux是目前世界上使用最多的Linux操作系统。因为它具备最好的图形界面,无论是安装、配置还是使用都十分方便,而且运行稳定,因此不论是新手还是老玩家都对它有很高的…

linux中网页播放音乐,Linux_在Linux系统下播放网页中的背景音乐技巧,在Linux中的firefox浏览许多网页 - phpStudy...

在Linux系统下播放网页中的背景音乐技巧在Linux中的firefox浏览许多网页时,很多使用了基于WMP的背景音乐播放器,如部份baidu空间。但firefox默认不支持播放。在LINUX的源中,有一为kaffeine-mozilla-plugin,能在firefox中使用kaffe…

简单了解linux,linux简单了解

今天主要了解下linuxlinux目录结构包含:/:代表根目录bin(binaries):存放二进制可执行文件sbin(super user binaries):存放二进制可执行文件,只有root才能访问etc(etcetera)存放系统配置文件usr(unix shared resource):用于存放共享…

C语言多个变量运算存储过程,postgresql函数中的赋值运算和postgresql函数存储过程实现数据批量插入...

今天检查大家写的postgresql函数时,发现有的家伙不遵守postgresql关于函数中的赋值运算表示方式:variable : value注意的是,是:表示赋值运算,而不是单独一个等号。这一点和delphi相似?另外,postgresql中关于…

c语言盗取qq号程序,C++获取本机登陆过的QQ号码示例程序

// FileName: GetQQ.cpp#include "stdafx.h" // 如果编译出错请删除这句#include "GetQQ.h"GetQQ::GetQQ(){}GetQQ::~GetQQ(){}std::vector GetQQ::Init(void){TCHAR pathBuffer[MAX_PATH] {0};::SHGetSpecialFolderPath(NULL, pathBuffer…

夫曼编码译码系统课程设计实验报告(含源代码c++_c语言),哈夫曼编码译码系统课程设计实验报告(含源代码C++_C语言)[1]...

目 录摘 要 ………………………………………………………………………..……………… II Abstract …………………………………………………………………………..………... II 第一章 课题描述………………………..………………………………………………….. 1 1.1 问题描述………

二级c语言评分标准一样吗,计算机二级评分严格吗 步骤错了有分吗

计算机二级考试不是人工阅卷,是由计算机评分,所以是很严格的。计算机二级机器阅卷主要看的是最后的输出文件,如果步骤错了导致结果不正确的话,也是没有分的。计算机二级评分标准计算机二级考试是电脑自动评分的,即上机…

c语言a 寻路算法,JS/HTML5游戏常用算法之路径搜索算法 A*寻路算法完整实例

本文实例讲述了JS/HTML5游戏常用算法之路径搜索算法 A*寻路算法。分享给大家供大家参考,具体如下:完整实例代码如下:A*寻路算法#stage {border: 1px solid lightgray;}window.onload function () {var stage document.querySelector(#stage…

在wsl下运行c语言,在Windows10通过WSL架设linux/gcc c语言学习环境

在Windows10通过WSL架设linux/gcc c语言学习环境零:简单介绍与先决条件中国一般的大学C课程中都使用很过时的编译器来进行教授——比如说什么visual studio 6.0啊,dev c啊。以笔者的眼界所看到的C语言教程中,大多都建议学习者在Linux环境下学…

android二级联动购物车,Android实现二级购物车的全选加反选、总价功能

本文实例为大家分享了Android实现二级购物车的全选加反选、总价的具体代码,供大家参考,具体内容如下MainActivityimport android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.support.v7.widget.LinearLayoutManager;imp…

android studio gradle 自动更新,android studio gradle 两种更新方法更新

又快一年没有使用AS了,正好要查看一个Android项目代码,干脆升级到新版试试看新变化,旧版本为3.1.2,目前最新版本为3.4.2,AS的升级是很简单的直接update等待即可.升级完了,打开GIT下载的项目,启动…

android安全 报告,Android安全检测报告

1.高危 Intent Scheme URL攻击详情:恶意页面可以通过Intent scheme URL执行基于Intent的攻击建议:将Intent的component/selector设置为null2.高危 WebView应用克隆风险详情:APP使用WebView访问网络,当开启了允许JS脚本访问本地文件…

android定时循环,Android AlarmManager实现定时循环后台任务

这篇文章使用AlarmManager实现了Android定时后台循环任务。使用场景:项目需要app每隔一段时间就去服务端请求一次接口,从而更新本地保存的信息。AlarmManager简介AlarmManager是Android中常用的一种系统级别的提示服务,在特定的时刻为我们广播…

android 线性布局蒙层,Android开发 - 掌握ConstraintLayout(一)传统布局的问题

在传统的Android开发中,页面布局占用了我们很多的开发时间,而且面对复杂页面的时候,传统的一些布局会显得非常复杂,每种布局都有特定的应用场景,我们通常需要各种布局结合起来使用来实现复杂的页面。随着ConstraintLay…

android sdk 4.4.4,4.4.4 not in Android SDK manager

问题I need to do some testing with 4.4.4 and it isnt available in my Android SDK Manager?Any ideas on what Im doing wrong?回答1:There is no problem at you Android SDK Manager, you just have to download the API 19.The API 19 is used by all the KitKat devi…

android7.0提示定位,解决android7.0上某些PopuWindow显示位置不正确的问题

网上关于android系统7.0的popupwindow适配的解决方案,基本都是一样的,就是重写PopupWindow里面的方法但是如何进行重写,对于一个初次接触的人来说,是个很头疼的问题。一来是涉及到java基础,二来是涉及到popupwindow的源…