c++虚函数内存模型(面试入坑之后的通透)
Posted 头号理想
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++虚函数内存模型(面试入坑之后的通透)相关的知识,希望对你有一定的参考价值。
昨天腾讯复试面试官对我一顿爆锤 其中就有c++对象模型
然后我决定研究一下这方面的知识 同时也记录下来 分享给大家
开始
在开始之前我先介绍一些常量和函数的知识
对于一个只包含非静态成员变量和普通成员变量的函数的类
class T
void fun() ;
void fun_1() ;
int var;
;
就像上边这样的
成员函数放在代码区,为该类的所有实例化出来的 对象所共有
每次新建一个对象都会新建一块内存区来存放var的值,调用成员函数的时候,
程序根据类型找到相应的代码区中所对应的函数进行调用
那么虚函数是怎么实现的呢?
如果一个类中含有虚函数,并且他创建了一个对象,这个对象所占内存一定比没有虚函数之前实例化出来的时候大4
(在后边的例子会测试)
多出来的4就是实现虚函数的关键,虚函数表指针
它指向虚函数表,而虚函数表中存放的都是指针,指向虚函数fun_b()的地址 然后我们就可以实现调用
注意:
普通函数,虚函数,虚函数表都是同一个类中所有对象所共有的,只有成员变量和虚函数表指针是每个对象所私有的
当一个类中有多个虚函数的时候,虚函数表仍然只有一个,虚函数表指针也只有一个,
不同的是虚函数表中会多一个指针变量,指向虚函数实现的区域的地址
通过对象内存中的虚函数表指针找到虚函数表,通过虚函数表中找到对应虚函数的实现区域进行调用
构造函数和析构函数可以为虚函数吗?
构造函数不能为虚函数,析构函数最好设置成为虚函数!
原因:
我们知道虚函数的实现就是通过对象内存中的虚函数表指针实现的
但是构造函数是用来实例化一个对象的,所以在这个时候,虚函数表指针是没有值的 所以构造函数不能成为虚函数
析构函数如果不是虚函数,那我们只会调用基类的析构函数,造成内存泄漏的问题
当我们继承之后 虚表会怎么变化呢?
我们要知道每个类都会对应一个虚函数表 如果在派生类中我们重写虚函数
那么这个虚函数表就会发生改变,而基类的虚函数表不会收到影响
在一个对象中 虚函数指针一般处于对象的开头
为了在查找虚函数表的时候更加迅速
最最简单的类 只有成员变量
首先我们简单写一个类 然后使用c++内置方法 查看内存中的相对位置
除了要在运行时确定虚函数地址外,还需要提供运行时的类型信息RTTI
为了避免虚函数表长度对RTTI的影响 所以RTTI位于虚函数表的开始位置
class Base1
public:
int base1_1;
int base1_2;
;
int main()
cout <<"sizeof(Base1) :"<< sizeof(Base1) << endl;
cout <<"offsetof(Base1, base1_1) :"<< offsetof(Base1, base1_1) << endl;
cout << "offsetof(Base1, base1_2) :"<<offsetof(Base1, base1_2) << endl;
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210326094406508.png
可以看到 成员对象是按顺序来保存的,最先声明的在上边 然后一次保存
类的大小就是成员变量大小之和 8=4+4
类中添加简单成员函数
class Base1
public:
int base1_1;
int base1_2;
void foo() ;
;
int main()
cout <<"sizeof(Base1) :"<< sizeof(Base1) << endl;
cout <<"offsetof(Base1, base1_1) :"<< offsetof(Base1, base1_1) << endl;
cout << "offsetof(Base1, base1_2) :"<<offsetof(Base1, base1_2) << endl;
为啥这次结果和上次是一样的呢?
因为如果一个函数知识普通的成员函数,那么它就不会发生动态绑定,也不会对布局产生任何影响
当我们调用一个非虚函数的时候,调用的一定是当前指针类型拥有的成员函数,在编译时期就确定下来
插入一个虚函数
class Base1
public:
int base1_1;
int base1_2;
void foo() ;
virtual void base1_fun1() ;
;
int main()
cout <<"sizeof(Base1) :"<< sizeof(Base1) << endl;
cout <<"offsetof(Base1, base1_1) :"<< offsetof(Base1, base1_1) << endl;
cout << "offsetof(Base1, base1_2) :"<<offsetof(Base1, base1_2) << endl;
可以看到上边莫名其妙少了4个字节的内存
这就是我们常说的虚函数表指针,类型为void** 说明他指向一个函数指针数组(之后会说明)
在上边的基础上再加一个虚函数
class Base1
public:
int base1_1;
int base1_2;
void foo() ;
virtual void base1_fun1() ;
virtual void base1_fun2() ;
;
int main()
cout <<"sizeof(Base1) :"<< sizeof(Base1) << endl;
cout <<"offsetof(Base1, base1_1) :"<< offsetof(Base1, base1_1) << endl;
cout << "offsetof(Base1, base1_2) :"<<offsetof(Base1, base1_2) << endl;
为什么没发生改变呢?
这就更肯定了刚刚我们的结论,如果再加一个虚函数,仅仅是给虚函数表中加一项
并不会影响到类中对象的大小的布局情况
假如说我们实例化出来两个对象 他俩的地址一定是不同的 但是他俩的虚函数指针指向的地方一定是相同的(同一个虚函数表)
通过上边的测试 我们可以得出一个结论
同一个类的不同实例公用一个虚函数表,他们都通过虚函数表指针指向虚函数表
那么问题来了 虚函数表保存在哪里呢?
他是在编译时期为我们创建好的 只存在一份
定义对象的时候,编译器自动将类对象的指针指向这个虚函数表
继承中的内存布局
class Base1
public:
int base1_1;
int base1_2;
void foo() ;
virtual void base1_fun1() ;
virtual void base1_fun2() ;
;
class Derive1 : public Base1
public:
int derive1_1;
int derive1_2;
;
int main()
cout << "sizeof(Base1) :" << sizeof(Base1) << endl;
cout << "offsetof(Base1, base1_1) :" << offsetof(Base1, base1_1) << endl;
cout << "offsetof(Base1, base1_2) :" << offsetof(Base1, base1_2) << endl;
cout << "sizeof(Derive1) :" << sizeof(Derive1) << endl;
cout << "offsetof(Derive1, derive1_1) :" << offsetof(Derive1, derive1_1) << endl;
cout << "offsetof(Derive1, derive1_2) :" << offsetof(Derive1, derive1_2) << endl;
我们首先可以看到基类在上边,派生类在下边
然后我们细看内存分布
其实虚函数指针只有基类的一个
这篇文章暂时就到这里 经过这次学习我对虚函数模型有了新的认识
所以大家还是学知识的时候如果可以就往深学习一些
希望我所写的对大家有帮助~
以上是关于c++虚函数内存模型(面试入坑之后的通透)的主要内容,如果未能解决你的问题,请参考以下文章