当前位置: 首页 > news >正文

浅谈c++函数调用以及析构函数为虚函数的原因

文章目录

  • 前言
  • 一、成员函数调用
    • 1.普通成员函数调用
    • 2.虚成员函数的调用方式
  • 二、静态绑定与动态绑定
    • 1.概念
  • 三、基类析构函数为虚函数原因
  • 总结


前言

巩固c++知识点,从编译器的角度看看程序的函数调用。


一、成员函数调用

1.普通成员函数调用

示例

class base
{
public:/*virtual void myfun1(){printf("base::myfun1()\n");}virtual void myfun2(int value = 1){printf("base::myfun2()=%d\n",value);}*/base(){printf("base::base()\n");}void myfun3(){m_i = 5;printf("base::myfun3()\n");}~base(){printf("base::~base()\n");}int m_i;
};int main()
{base base;base.myfun3();return 0;
}

反汇编查看函数调用:
在这里插入图片描述
将base对象放入寄存器中,再通过函数地址调用函数。由此看出,成员函数,和普通的函数其实并没有太多的区别。只是因为函数内部,要访问类成员变量,所以通过寄存器将自身的地址传入到函数中,这样在函数中就能访问成员变量。

2.虚成员函数的调用方式

示例:

class base1
{
public:virtual void myfun1(){printf("base1::myfun1()\n");}virtual void myfun2(int value = 1){printf("base1::myfun2()=%d\n",value);}void myfun3(){printf("base::myfun3()\n");}int m_i1 = 5;
};class base2 :public base1
{
public:base2(){printf("test\n");}virtual void myfun1(){printf("base2::myfun1()\n");}virtual void myfun2(int value = 2){printf("base::myfun2()=%d\n", value);}void myfun3(){printf("base2::myfun3()\n");}virtual ~base2(){printf("~base2::base2()\n");}int m_i3 = 6;
};

当通过子对象调用,直接调用虚函数时:

base2 base2;
base2.myfun1();

在这里插入图片描述
通过以上反汇编的结果,可以知道,通过定义的对象访问虚函数时,编译器直接通过函数地址来进行调用的。

当通过类指针访问虚函数时:

	base2 *pbase2 = new base2();pbase2->myfun1();base1 *pbase1 = new base2();pbase1->myfun1();

当子类指针指向子类对象时,调用虚函数反汇编如下:
在这里插入图片描述
因为是子类指针,所以子类对象首地址会放入寄存器中。获取虚函数表,放入rax中。最后通过虚函数表找到函数地址,进行调用。
关于虚函数的调用,可参考:https://blog.csdn.net/qq_39884728/article/details/143563901

当父类指针指向子类时,查看反汇编:
在这里插入图片描述
pbase1就是子类对象,通过观察也是通过虚函数表指针调用的。

二、静态绑定与动态绑定

1.概念

在这里插入图片描述
在这里插入图片描述
当一个子类和一个父类都有一个相同名字的成员函数时,此时调用那个函数,根据静态绑定相关。
示例:

base2 *pbase2 = new base2();pbase2->myfun3();base1 *pbase1 = new base2();pbase1->myfun3();

反汇编查看pbase2->myfun3() 如下:
在这里插入图片描述
因为是子类指针,所以此时调用子类的成员函数。

反汇编查看pbase1->myfun3() 如下:
在这里插入图片描述
因为是父类指针,所以此时调用父类的成员函数。

虚函数的动态绑定:

	base2 *pbase2 = new base2();pbase2->myfun1();base1 *pbase1 = new base2();pbase1->myfun1();

输出结果:
在这里插入图片描述
因为都是子类的对象,同时子类重写了虚函数,更改了虚函数表,不和父类的一样。所以调用的是子类的方法。

缺省参数是静态绑定的:
示例:

	base2 *pbase2 = new base2();pbase2->myfun2();base1 *pbase1 = new base2();pbase1->myfun2();

输出结果:
在这里插入图片描述
通过结果可知,缺省参数的输入,和静态绑定的指针类型有关。

三、基类析构函数为虚函数原因

示例:

class base1
{
public:virtual void myfun1(){printf("base1::myfun1()\n");}virtual void myfun2(int value = 1){printf("base1::myfun2()=%d\n",value);}void myfun3(){printf("base::myfun3()\n");}virtual ~base1(){printf("~base1::~base1()\n");}int m_i1 = 5;
};class base2 :public base1
{
public:base2(){printf("test\n");}virtual void myfun1(){printf("base2::myfun1()\n");}virtual void myfun2(int value = 2){printf("base::myfun2()=%d\n", value);}void myfun3(){printf("base2::myfun3()\n");}virtual ~base2(){printf("~base2::base2()\n");}int m_i3 = 6;
};
int main()
{base1 *pbase2 = new base2();delete pbase2;return 0;
}

反汇编查看base2的析构函数如下:
在这里插入图片描述
在base2的析构函数结束后,会调用base1的析构函数。
观察父类析构函数的反汇编:
在这里插入图片描述
可以看到父类的析构函数,只存在对自己的操作。

当父类的析构函数不是虚函数时,那么根据静态绑定的定义。此时删除指针时,不会调用虚函数表,而是直接调用父类的析构函数。导致子类的析构函数没被调用。
由此推断,当对象是子类对象时,只要调用到子类的析构函数,(结束时会调用父类的析构函数)对象就能完全释放。
那么根据静态绑定的原则,当子类指针,指向子类对象时。删除指针,此时,会调用子类的析构函数。

示例:
将上面的虚析构函数,改为析构函数

base2 *pbase2 = new base2();delete pbase2;

结果:
在这里插入图片描述


总结

简单的总结了一下函数调用,文章关于虚函数的内容,并没有进行详细的解释。如有好奇的读者,可参考:
链接: 浅谈c++虚函数

相关博客:
浅谈c++虚基类内存布局


http://www.mrgr.cn/news/71024.html

相关文章:

  • Spring Boot 接口防重复提交解决方案
  • 猫头虎分享: 小米大模型升级第二代MiLM2:从一代到二代,能力飞跃提升
  • Jmeter中的定时器(二)
  • 【目标检测】【Ultralytics-YOLO系列】Windows11下YOLOV5人脸目标检测
  • 【activiti工作流源码集成】springboot+activiti+mysql+vue+redis工作流审批流集成整合业务绑定表单流程图会签驳回
  • 阿里巴巴通义灵码推出Lingma SWE-GPT:开源模型的性能新标杆
  • 基于Ubuntu2410脚本搭建OpenStack-D版
  • 青训5_1112_01 小S的倒排索引(内置方法 set(a) set(b) 及sorted 排序)
  • No module named ‘torch.nn.attention‘
  • 【C++】C++基础知识
  • 期权懂|你知道场外个股期权该如何参与吗?
  • 微服务改造:踩过的坑!
  • 2. Sharding-JDBC广播表和绑定表操作
  • 阿里云Linux安装Docker服务报错问题
  • 【轻松远程处理图片:在线图片编辑工具Photopea群晖NAS部署解决方案】
  • 解决 C/C++ 中 “invalid use of incomplete type” 编译错误
  • 【前端】深入浅出的React.js详解
  • Spring Boot编程训练系统:深入设计与实现
  • 双指针算法的妙用:提高代码效率的秘密(3)
  • 【三宝的身高】
  • 数据湖系列之四 | 数据湖存储加速方案的发展和对比分析
  • C# 后端方法返回时间戳
  • 2025年河南定向选调生报名时间
  • java ssm 个人学习管理系统 学习安排 学生在线学习管理 源码 jsp
  • 【GDB调试】智慧中控项目的调试
  • 【Linux进程篇4】谈:操作系统进程调度各种基本状态(运行,挂起,阻塞等)