面试题答案
一键面试虚函数表的存储位置与类对象布局的关系
- 虚函数表存储位置:在C++中,虚函数表(vtable)存储在程序的只读数据段(通常称为.rodata段),这是因为虚函数表内容在程序运行期间不会改变,将其放在只读数据段可防止意外修改。
- 类对象布局与虚函数表指针:对于包含虚函数的类,其对象布局的第一个成员是虚函数表指针(vptr)。这个指针指向该类对应的虚函数表。例如,假设有类
Base
:
class Base {
public:
virtual void func() {}
};
Base
类对象在内存中的布局首先是vptr
,然后才是其他成员变量。这种布局使得在运行时能够快速定位虚函数表,进而找到要调用的虚函数。
不同存储位置成员变量对虚函数表指针位置及对象内存布局的影响
- 静态成员变量:静态成员变量不属于类对象的一部分,它们存储在全局数据区,与类对象的内存布局无关。因此,静态成员变量不会影响虚函数表指针的位置和类对象在内存中的布局。例如:
class Example {
public:
static int s_var;
virtual void func() {}
};
这里Example
类对象布局依然是vptr
在前,不受static int s_var
的影响。
2. 普通成员变量:普通成员变量按照声明顺序依次存储在虚函数表指针之后。例如:
class AnotherExample {
public:
virtual void func() {}
int a;
double b;
};
AnotherExample
类对象在内存中的布局是vptr
、a
、b
。这种布局保证了对象内存布局的一致性和可预测性,编译器能够根据偏移量准确访问各个成员变量。
这种布局对运行时多态实现的底层原理
- 动态绑定基础:运行时多态通过虚函数和虚函数表实现。当使用基类指针或引用调用虚函数时,程序运行时会根据对象实际类型(而非指针或引用类型)来确定调用的具体函数。由于对象布局中
vptr
位于首位,通过对象地址即可快速获取虚函数表指针,进而定位到虚函数表。 - 具体过程:假设存在继承关系
Base
(基类)和Derived
(派生类),Derived
重写了Base
的虚函数。当有Base* ptr = new Derived();
,ptr->func();
调用时:- 首先,根据
ptr
指向的对象地址,找到对象布局中的vptr
。 - 然后,通过
vptr
找到Derived
类的虚函数表。 - 最后,在虚函数表中找到被重写的
func
函数的地址并调用。 这种基于对象布局和虚函数表的机制,使得C++能够在运行时动态地确定要调用的虚函数,实现运行时多态。
- 首先,根据