文章目录
一、没有虚函数的对象,它的内存分布。
#include <stdio.h>
#include <stddef.h> // 定义了offsetof宏
class Base {
public:
int m_val1;
int m_val2;
void fun() {}
};
int main()
{
Base b;
int offset1 = offsetof(Base, m_val1);
int offset2 = offsetof(Base, m_val2);
b.fun();
return 0;
}
对象大小及偏移:
sizeof(Base) | 8 |
---|---|
offsetof(Base, m_val1) | 0 |
offsetof(Base, m_val2) | 4 |
如图:
- 成员变量是按照定义的顺序来保存的,最先声明的在最上边,然后依次保存,类对象的大小就是所有成员变量大小之和。
- 如果对象没有虚函数,那么它就不可能会发生动态绑定,也就不会对对象的内存布局造成任何影响。
二、拥有一个虚函数的类对象。
class Base {
public:
int m_val1;
int m_val2;
virtual void fun() {}
};
类对象中多了个虚函数表指针
:
__vfptr
的定义伪代码大概如下:
// 上图中可看出,虚函数表中会多存放一个元素
void* __fun[2] = { &Base::b_fun };
const void** __vfptr = &__fun[0];
对象大小及偏移:
sizeof(Base) | 12 |
---|---|
offsetof(Base, m_val1) | 4 |
offsetof(Base, m_val2) | 8 |
__vfptr
被定义成一个指向指针数组的指针,数组的长度是虚函数个数+1
__vfptr
前面有const修饰,它修饰的是那个虚函数表。
三、拥有多个虚函数的类对象。
class Base {
public:
int m_val1;
int m_val2;
virtual void fun1() {}
virtual void fun2() {}
};
虚函数表中增加了第2个元素, 其值为第2个虚函数fun2()
的函数地址:
对象大小及偏移:
sizeof(Base) | 12 |
---|---|
offsetof(Base, m_val1) | 4 |
offsetof(Base, m_val2) | 8 |
增加一个虚函数, 只是简单地向该类对应的虚函数表中增加一项而已, 并不会影响到类对象的大小以及布局情况。
四、同一个类的不同实例,共用同一份虚函数表。
#include <stdio.h>
#include <stddef.h> // 定义了offsetof宏
class Base {
public:
int m_val1;
int m_val2;
virtual void fun1() {}
virtual void fun2() {}
};
int main()
{
Base b1;
Base b2;
int offset1 = offsetof(Base, m_val1);
int offset2 = offsetof(Base, m_val2);
b1.fun1();
return 0;
}
两个对象的虚函数成员如下:
同一个类的不同实例共用同一份虚函数表,它们都通过一个虚函数表指针
__vfptr
指向该虚函数表。
五、基类定义了虚函数,派生类没有新增虚函数。
class Base {
public:
int m_val1;
int m_val2;
virtual void fun1() {}
virtual void fun2() {}
};
class Driver : public Base {
public:
int m_val3;
int m_val4;
};
派生类对象内存分布:
基类在上边, 继承类的成员在下边依次定义。
六、派生类覆盖基类虚函数。
#include <stdio.h>
#include <stddef.h> // 定义了offsetof宏
class Base {
public:
int m_val1;
int m_val2;
virtual void fun1() {}
virtual void fun2() {}
};
class Driver : public Base {
public:
int m_val3;
int m_val4;
virtual void fun1() {}
};
int main()
{
Driver d;
d.fun1();
return 0;
}
派生类对象的func1()
地址发生改变,指向新的实现地址:
通过基类指针指向派生类对象时,调用的是继承后被覆盖的函数,这就是多态。
七、派生类新增虚函数。
#include <stdio.h>
#include <stddef.h> // 定义了offsetof宏
class Base {
public:
int m_val1;
int m_val2;
virtual void fun1() {}
virtual void fun2() {}
};
class Driver : public Base {
public:
int m_val3;
int m_val4;
virtual void fun3() {}
};
int main()
{
Driver d;
int offset1 = offsetof(Driver, m_val3);
int offset2 = offsetof(Driver, m_val4);
d.fun1();
return 0;
}
对象大小及偏移:
sizeof(Driver) | 20 |
---|---|
offsetof(Driver, m_val3) | 12 |
offsetof(Driver, m_val4) | 16 |
__vfptr
数组大小成变了4
,新增了派生类的虚函数地址,但是Base部分只展示基类的虚函数,所以没有窗口中展示。
派生类新增的虚函数被加在基类虚函数表的后面!!
八、如果发生多继承,派生类中新增的虚函数应该加在哪?覆盖呢?
#include <stdio.h>
#include <stddef.h> // 定义了offsetof宏
class BaseA {
public:
int m_val1;
int m_val2;
virtual void fun1() {}
virtual void fun2() {}
};
class BaseB {
public:
int m_val3;
int m_val4;
virtual void fun3() {}
virtual void fun4() {}
};
class Driver: public BaseA, public BaseB {
public:
virtual void fun1() {} // 覆盖BaseA::func1()
virtual void fun3() {} // 覆盖BaseB::func3()
virtual void fun5() {} // 新增virtual func5()
};
int main()
{
Driver d;
d.fun1();
return 0;
}
派生类内存布局:
1. 覆盖了哪个基类,就修改对应的虚函数表。
2. 新增的虚函数加在第一个虚函数表之后。
3. 如果基类中都没有虚函数,而派生类中有,则派生类对象中同样存在虚函数表,并且是在最前面。