【反汇编分析】C++成员函数和虚函数

本节通过反汇编研究C++非static成员函数和虚函数的执行流程;

代码片段如下

class Animal
{
public:
  virtual void print()
  {
    cout << "Animal::print "<< endl;
  }

  void print2()
  {
    cout << "Animal::print2 "<< endl;
  }
};

class Dog : public Animal
{
public:
  Dog():_age(1)
  {

  }

  virtual void print()
  {
    cout << "Dog::print " << _age << endl;
  }

  void print2()
  {
    cout << "Dog::print2 "<< endl;
  }
private:
  int _age;
};

int main(void)
{
  Dog d;
  Animal* p1 = &d;

  p1->print();
  p1->print2();

  cout << "_age address: "<< ((int*)&d+1) << " value: " << *((int*)&d+1)<< endl;
  cout << "virtual fun address: "<< (int*)&d << endl;
  cout << "virtual fun[1] address: "<< (int*)*(int*)&d << endl;
  cout << "virtual fun[2] address: "<< ((int*)*(int*)&d+1) << endl;
}

输出如下:

[email protected]:~/Desktop$ ./test
Dog::print 1
Animal::print2
_age address: 0xbfc5c7f8 value: 1
virtual fun address: 0xbfc5c7f4
virtual fun[1] address: 0x8048c30
virtual fun[2] address: 0x8048c34

说明几点:

(1)本节只会研究p1->print()和p1->print2()的反汇编代码,对于mian函数的下面cout输出部分,只是为了验证;

Dog类的虚函数表示意图如下:

print和print2调用的函数栈帧如下:

main函数的反汇编

print和print2相关部分反汇编如下

0804882c <main>:
 804882c:	8d 4c 24 04          	lea    0x4(%esp),%ecx
 8048830:	83 e4 f0             	and    $0xfffffff0,%esp
 8048833:	ff 71 fc             	pushl  -0x4(%ecx)
 8048836:	55                   	push   %ebp         #放入ebp
 8048837:	89 e5                	mov    %esp,%ebp
 8048839:	53                   	push   %ebx
 804883a:	51                   	push   %ecx
 804883b:	83 ec 10             	sub    $0x10,%esp
 804883e:	83 ec 0c             	sub    $0xc,%esp
 8048841:	8d 45 ec             	lea    -0x14(%ebp),%eax
 8048844:	50                   	push   %eax
 8048845:	e8 0a 02 00 00       	call   8048a54 <_ZN3DogC1Ev>    #调用dog的构造函数
 804884a:	83 c4 10             	add    $0x10,%esp
 804884d:	8d 45 ec             	lea    -0x14(%ebp),%eax
 8048850:	89 45 f4             	mov    %eax,-0xc(%ebp)     #将Dog d的地址赋值给p1
 8048853:	8b 45 f4             	mov    -0xc(%ebp),%eax     #取p1
 8048856:	8b 00                	mov    (%eax),%eax         #取virtual虚函数表的地址
 8048858:	8b 00                	mov    (%eax),%eax         #取第一个虚函数的函数地址
 804885a:	83 ec 0c             	sub    $0xc,%esp
 804885d:	ff 75 f4             	pushl  -0xc(%ebp)          #将p1压入作为参数

 #取0x8048c3处的内容执行,即执行第一个虚函数,其实是函数地址08048a7e,即_ZN3Dog5printEv的函数地址,调用Dog::print
 8048860:	ff d0                	call   *%eax 

 8048862:	83 c4 10             	add    $0x10,%esp
 8048865:	83 ec 0c             	sub    $0xc,%esp
 8048868:	ff 75 f4             	pushl  -0xc(%ebp)
 804886b:	e8 a8 01 00 00       	call   8048a18 <_ZN6Animal6print2Ev>  #调用Animal::print2
 8048870:	83 c4 10             	add    $0x10,%esp
 8048873:	8d 45 ec             	lea    -0x14(%ebp),%eax

Dog的构造函数反汇编如下

08048a54 <_ZN3DogC1Ev>:
 8048a54:	55                   	push   %ebp
 8048a55:	89 e5                	mov    %esp,%ebp
 8048a57:	83 ec 08             	sub    $0x8,%esp
 8048a5a:	8b 45 08             	mov    0x8(%ebp),%eax     #取Dog d的地址
 8048a5d:	83 ec 0c             	sub    $0xc,%esp
 8048a60:	50                   	push   %eax
 8048a61:	e8 e0 ff ff ff       	call   8048a46 <_ZN6AnimalC1Ev> #调用Animal的构造函数
 8048a66:	83 c4 10             	add    $0x10,%esp
 8048a69:	8b 45 08             	mov    0x8(%ebp),%eax
 8048a6c:	c7 00 30 8c 04 08    	movl   $0x8048c30,(%eax)  #Dog虚函数表地址
 8048a72:	8b 45 08             	mov    0x8(%ebp),%eax
 8048a75:	c7 40 04 01 00 00 00 	movl   $0x1,0x4(%eax)   #_age为1
 8048a7c:	c9                   	leave
 8048a7d:	c3                   	ret    

Animal的构造函数反汇编如下

08048a46 <_ZN6AnimalC1Ev>:
 8048a46:	55                   	push   %ebp
 8048a47:	89 e5                	mov    %esp,%ebp
 8048a49:	8b 45 08             	mov    0x8(%ebp),%eax
 8048a4c:	c7 00 40 8c 04 08    	movl   $0x8048c40,(%eax)  #Animal虚函数表地址
 8048a52:	5d                   	pop    %ebp
 8048a53:	c3                   	ret    

Dog虚函数表反汇编如下

08048c28 <_ZTV3Dog>:
 8048c28:	00 00                	add    %al,(%eax)
 8048c2a:	00 00                	add    %al,(%eax)
 8048c2c:	4c                   	dec    %esp
 8048c2d:	8c 04 08             	mov    %es,(%eax,%ecx,1)
 8048c30:	7e 8a                	jle    8048bbc <_IO_stdin_used+0x28>
 8048c32:	04 08                	add    $0x8,%al
 8048c34:	00 00                	add    %al,(%eax)

说明几点:

(1)gcc采用小端存放,因此0x8048c30处的内容为0x08048a7e;具体如下

 8048c30:	7e 8a                	jle    8048bbc <_IO_stdin_used+0x28>
 8048c32:	04 08                	add    $0x8,%al
                小  大

(2)我们知道虚函数表的地址为0x8048c30,而print为Dog虚函数表的第一个函数,因此call   *%eax实际上执行的是0x8048c30地址存放的函数地址,即为08048a7e地址,正好对应Dog::print的函数地址;

Dog::print函数反汇编如下

08048a7e <_ZN3Dog5printEv>:
 8048a7e:	55                   	push   %ebp
 8048a7f:	89 e5                	mov    %esp,%ebp
 8048a81:	53                   	push   %ebx
 8048a82:	83 ec 04             	sub    $0x4,%esp
 8048a85:	8b 45 08             	mov    0x8(%ebp),%eax   #获得Dog d的地址
 8048a88:	8b 58 04             	mov    0x4(%eax),%ebx   #此时_age的值在ebx中
 8048a8b:	83 ec 08             	sub    $0x8,%esp
 8048a8e:	68 b7 8b 04 08       	push   $0x8048bb7
 8048a93:	68 80 91 04 08       	push   $0x8049180   #调用cout的地址
 8048a98:	e8 1f fc ff ff       	call   80486bc <[email protected]>
 8048a9d:	83 c4 10             	add    $0x10,%esp
 8048aa0:	83 ec 08             	sub    $0x8,%esp
 8048aa3:	53                   	push   %ebx
 8048aa4:	50                   	push   %eax
 8048aa5:	e8 b2 fb ff ff       	call   804865c <[email protected]>
 8048aaa:	83 c4 10             	add    $0x10,%esp
 8048aad:	83 ec 08             	sub    $0x8,%esp
 8048ab0:	68 ec 86 04 08       	push   $0x80486ec
 8048ab5:	50                   	push   %eax
 8048ab6:	e8 21 fc ff ff       	call   80486dc <[email protected]>
 8048abb:	83 c4 10             	add    $0x10,%esp
 8048abe:	8b 5d fc             	mov    -0x4(%ebp),%ebx
 8048ac1:	c9                   	leave
 8048ac2:	c3                   	ret    

Animal::print2函数反汇编如下

08048a18 <_ZN6Animal6print2Ev>:
 8048a18:	55                   	push   %ebp
 8048a19:	89 e5                	mov    %esp,%ebp
 8048a1b:	83 ec 08             	sub    $0x8,%esp
 8048a1e:	83 ec 08             	sub    $0x8,%esp
 8048a21:	68 a7 8b 04 08       	push   $0x8048ba7
 8048a26:	68 80 91 04 08       	push   $0x8049180
 8048a2b:	e8 8c fc ff ff       	call   80486bc <[email protected]>
 8048a30:	83 c4 10             	add    $0x10,%esp
 8048a33:	83 ec 08             	sub    $0x8,%esp
 8048a36:	68 ec 86 04 08       	push   $0x80486ec
 8048a3b:	50                   	push   %eax
 8048a3c:	e8 9b fc ff ff       	call   80486dc <[email protected]>
 8048a41:	83 c4 10             	add    $0x10,%esp
 8048a44:	c9                   	leave
 8048a45:	c3                   	ret    
时间: 2024-08-08 05:34:44

【反汇编分析】C++成员函数和虚函数的相关文章

【继承与多态】C++:继承中的赋值兼容规则,子类的成员函数,虚函数(重写),多态

实现基类(父类)以及派生类(子类),验证继承与转换--赋值兼容规则: 子类对象可以赋值给父类对象(切割/切片) 父类对象不能赋值给子类对象 父类的指针/引用可以指向子类对象 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成) #include<iostream> using namespace std; class People    //父类或者基类 { public:     void Display()     {         cout << "_na

[转]什么是C++虚函数、虚函数的作用和使用方法

我们知道,在同一类中是不能定义两个名字相同.参数个数和类型都相同的函数的,否则就是“重复定义”.但是在类的继承层次结构中,在不同的层次中可以出现名字相同.参数个数和类型都相同而功能不同的函数.例如在例12.1(具体代码请查看:C++多态性的一个典型例子)程序中,在Circle类中定义了 area函数,在Circle类的派生类Cylinder中也定义了一个area函数.这两个函数不仅名字相同,而且参数个数相同(均为0),但功能不同,函数体是不同的.前者的作用是求圆面积,后者的作用是求圆柱体的表面积

什么是C++虚函数、虚函数的作用和使用方法

我们知道,在同一类中是不能定义两个名字相同.参数个数和类型都相同的函数的,否则就是“重复定义”.但是在类的继承层次结构中,在不同的层次中可以出现名字相同.参数个数和类型都相同而功能不同的函数.例如在例12.1(具体代码请查看:C++多态性的一个典型例子)程序中,在Circle类中定义了 area函数,在Circle类的派生类Cylinder中也定义了一个area函数.这两个函数不仅名字相同,而且参数个数相同(均为0),但功能不同,函数体是不同的.前者的作用是求圆面积,后者的作用是求圆柱体的表面积

对于Java中main函数为虚函数以及多态的一点理解

Question: 1.在Java中,由于main函数的定义为: public static void main(String[] args):那么要想在main函数中调用其余的函数的话,就必须将定义为static.另外,调用其他类编写的成员函数时,却不需要该成员函数为静态的,这是为什么? 分析: 静态方法是属于某一个类所有,而非静态方法是属于某类的对象所有.也就是说,要想调用非静态方法,必须先调用new来得到一个类的对象,系统为其分配内存,然后才能通过该对象访问相应的非静态成员函数.而静态方法

C++纯虚函数、虚函数、实函数、抽象类,重载、重写、重定义

首先,面向对象程序设计(object-oriented programming)的核心思想是数据抽象.继承.动态绑定.通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不完全相同的新类,使用动态绑定,可以在一定程度上忽略相似类的区别,而以统一的方式使用它们的对象. 虚函数的作用是实现多态性(Polymorphism),多态性是将接口与实现进行分离,采用共同的方法,但因个体差异而采用不同的策略.纯虚函数则是一种特殊的虚函数.虚函数联系到多态,多态联系到继承. 一.虚函

C++ 虚函数&amp;纯虚函数&amp;抽象类&amp;接口&amp;虚基类(转)

http://www.cnblogs.com/fly1988happy/archive/2012/09/25/2701237.html 1. 多态 在面向对象语言中,接口的多种不同实现方式即为多态.多态是指,用父类的指针指向子类的实例(对象),然后通过父类的指针调用实际子类的成员函数. 多态性就是允许将子类类型的指针赋值给父类类型的指针,多态是通过虚函数实现的. 多态可以让父类的指针有“多种形态”,这是一种泛型技术.(所谓泛型技术,就是试图使用不变的代码来实现可变的算法). 2. 虚函数 2.1

(转)内联(inline)函数与虚函数(virtual)的讨论

本文转自: http://topic.csdn.net/t/20051220/09/4469273.html 函数的inline属性是在编译时确定的, 然而,virtual的性质是在运行时确定的,这两个不能同时存在,只能有一个选择,文件中的inline关键字只是对编译器的建议,编译器是否采纳是编译器的事情. 1.内联函数是个静态行为,而虚函数是个动态行为,他们之间是有矛盾的. 2.我们之所以能看到一些象内联函数的虚函数,是因为某个函数是否是内联函数不是由我们说的算,而是由编译器决定的.我们只能向

C++中的纯虚函数和虚函数的作用

1. 虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class). 2. 虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义. 3. 虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式

虚函数/纯虚函数/抽象类/接口/虚基类

1.多态 在面向对象语言中,接口的多种不同实现方式即为多态.多态是指,用父类的指针指向子类的实例(对象),然后通过父类的指针调用实际子类的成员函数. 在Java中,没有指针,就直接用父类实例化子类对象 多态性就是允许将子类类型的指针赋值给父类类型的指针,多态是通过虚函数实现的,多态可以让父类的指针有“多种形态”,这是一种泛型技术. 所谓泛型技术,就是试图使用不变的代码来实现可变的算法 2.虚函数 在基类的类定义中,定义虚函数的一般形式: Virtual 函数返回值类型 虚函数名(形参表){ 函数