C++虚函数机制(Linux版)


一、准备工作

实验环境及使用的软件版本:

$ uname -a

Linux 2.6.32-431.el6.x86_64 #1 SMP Fri Nov 22 03:15:09 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

$ cat /etc/issue

CentOS release 6.5 (Final)

$ g++ -v

Target: x86_64-redhat-linux

……

Thread model: posix

gcc version 4.4.7 20120313 (Red Hat 4.4.7-17) (GCC)

$ gdb -v

GNU gdb (GDB) Red Hat Enterprise Linux (7.2-90.el6)

……

 

示例代码: virtual_fun.cpp (单一继承模型)

#include <iostream>

using namespace std;

 

#define tracepoint() cout<<"line="<<__LINE__<<",func="<<__FUNCTION__<<endl;

 

//基类

class CBase

{

public:

         CBase();

         ~CBase();

public:

         virtual void vFun1();

         virtual void vFun2();

         virtual void vFun3();

public:

         void baseFun();

};

 

CBase::CBase()

{       

         tracepoint();

}

CBase::~CBase()

{       

         tracepoint();

}

void CBase::vFun1()

{

         tracepoint();

}

void CBase::vFun2()

{

         tracepoint();

}

void CBase::vFun3()

{

         tracepoint();

}

void CBase::baseFun()

{

         tracepoint();

}

 

//派生类

class CDerived:public CBase

{

public:

         CDerived();

         ~CDerived();

public:

         virtual void vFun1();

         virtual void vFun2();

public:

         void derivedFun();

};

 

CDerived::CDerived()

{

         tracepoint();   

}

CDerived::~CDerived()

{

         tracepoint();

}

void CDerived::vFun1()

{

         tracepoint();

}

void CDerived::vFun2()

{

         tracepoint();

}

void CDerived::derivedFun()

{

         tracepoint();

}

 

 

int main()

{

        

         CDerived derived;   

         //通过基类指针调用派生类实现的虚函数

         CBase *pBase = &derived;

         pBase->vFun1();

         pBase->vFun2();      

        

         //通过基类指针调用派生类未实现的虚函数

         pBase->vFun3();

        

         //通过基类指针访问普通成员函数

         pBase->baseFun();  

         return 0;

}

 

编译并执行一下(为方便后面调试,在编译时加了-g选项):

$ g++ -g virtual_fun.cpp -o virtual_fun

$ ./virtual_fun

line=22,func=CBase

line=60,func=CDerived

line=68,func=vFun1

line=72,func=vFun2

line=38,func=vFun3

line=42,func=baseFun

line=64,func=~CDerived

line=26,func=~CBase

 

二、ELF文件虚表分析

先介绍一个知识点:

“g++在编译链接时,会为每个带虚函数的类生成一个虚表,该虚表类似类的静态只读数组成员。生成可执行文件后,类的“虚表地址”、及“虚表里填充的虚函数的地址”和“填充顺序”都已经确定下来,存放存放在可执行文件的.rodata

下面我们通过分析可执行文件来查看下:

$ file virtual_fun [注释:查看可执行文件的类型,可看出是ELF格式]

virtual_fun: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped

[注释:通过以下命令解析ELF可执行文件,分别得到基类、派生类的虚表信息

内容共八列每列对应的含义如下:

Num:    Value          Size Type    Bind   Vis      Ndx Name]

$ readelf -Ws virtual_fun | c++filt | grep vtable | grep Cderived    

    95: 0000000000401020    40 OBJECT  WEAK   DEFAULT   15 vtable for CDerived

$readelf -Ws virtual_fun | c++filt | grep vtable | grep CBase

   115: 0000000000401060    40 OBJECT  WEAK   DEFAULT   15 vtable for Cbase

 

其中0000000000401020 、 0000000000401060分别对应类的虚表16进制地址。

分别查看下里面的内容:

$gdb virtual_fun

GNU gdb (GDB) Red Hat Enterprise Linux (7.2-90.el6)

[注释:这里省略一些gdb的打印信息,此时函数仅仅是加载到内存,还没有运行哦----]

(gdb) x /5xg 0x0000000000401060 [注释:由于大小为40字节,我们打印58字节内存即可]

0x401060 <_ZTV5CBase>:  0x0000000000000000      0x00000000004010c0

0x401070 <_ZTV5CBase+16>:       0x0000000000400a4c      0x0000000000400a9e

0x401080 <_ZTV5CBase+32>:       0x0000000000400af0

(gdb) info symbol 0x00000000004010c0 [注释:查看对应地址的符号表]

typeinfo for CBase in section .rodata

(gdb) info symbol 0x0000000000400a4c

CBase::vFun1() in section .text

(gdb) info symbol 0x0000000000400a9e

CBase::vFun2() in section .text

(gdb) info symbol 0x0000000000400af0

CBase::vFun3() in section .text

[注释:可以看出虚表的前16个字节存放类的typeinfo信息,从后面开始每八个字节,按照声明的顺序存放一个虚函数的地址,真正的虚函数表地址是从0x401060+0x10 = 0x401070开始的,填充顺序为“基类”中虚函数的声明顺序。从后面的执行期汇编分析可看出,使用虚表时,编译器自动跳过16字节,汇编代码直接使用0x401070这个地址]

 

[注释:同理打印出派生类的虚表信息,真正虚函数表地址是从0x401020+0x10=0x401030开始的。派生类的虚函数延用基类虚函数的排序规则:有则替换,无则使用基类的,新增则按声明顺序往后排,并不改变原有顺序]

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值