面试题答案
一键面试1. 指针与虚函数表的协同工作原理
在 C++ 中,当一个类包含虚函数时,编译器会为该类生成一个虚函数表(vtable)。虚函数表是一个存储虚函数地址的数组。每个包含虚函数的类对象都有一个隐藏的指针,称为虚指针(vptr),该指针指向类的虚函数表。
当通过基类指针调用虚函数时,首先会根据指针所指向对象的实际类型,找到对应的虚函数表。然后,在虚函数表中查找与被调用虚函数对应的函数地址,并调用该函数。这种机制实现了动态绑定,即根据对象的实际类型来决定调用哪个虚函数。
2. 底层实现原理
- 虚函数表的生成:编译器为每个包含虚函数的类生成一个虚函数表。虚函数表中的条目按照虚函数在类定义中的声明顺序排列。
- 虚指针的初始化:在对象构造时,编译器会将对象的虚指针(vptr)初始化为指向该对象所属类的虚函数表。
- 虚函数调用:当通过基类指针调用虚函数时,编译器会根据指针所指向对象的虚指针找到对应的虚函数表,然后在虚函数表中查找并调用相应的虚函数。
3. 具体代码说明
#include <iostream>
class Base {
public:
virtual void virtualFunction() {
std::cout << "Base::virtualFunction" << std::endl;
}
};
class Derived : public Base {
public:
void virtualFunction() override {
std::cout << "Derived::virtualFunction" << std::endl;
}
};
int main() {
Base* basePtr = new Derived();
basePtr->virtualFunction(); // 动态绑定,调用 Derived::virtualFunction
delete basePtr;
return 0;
}
在上述代码中:
Base
类包含一个虚函数virtualFunction
。编译器会为Base
类生成一个虚函数表,并在Base
对象构造时初始化虚指针(vptr)指向该虚函数表。Derived
类继承自Base
类并重写了virtualFunction
。编译器会为Derived
类生成一个新的虚函数表,该表中virtualFunction
的条目指向Derived::virtualFunction
。Derived
对象构造时,虚指针(vptr)指向Derived
类的虚函数表。- 当
basePtr->virtualFunction()
被调用时,根据basePtr
所指向的Derived
对象的虚指针,找到Derived
类的虚函数表,进而调用Derived::virtualFunction
。
4. 绕过正常虚函数调用机制
虽然不推荐实际使用,但为了探讨原理,可以通过直接操作虚函数表指针来绕过正常的虚函数调用机制。以下是一个示例代码:
#include <iostream>
#include <cstdint>
class Base {
public:
virtual void virtualFunction() {
std::cout << "Base::virtualFunction" << std::endl;
}
};
class Derived : public Base {
public:
void virtualFunction() override {
std::cout << "Derived::virtualFunction" << std::endl;
}
};
typedef void (*VirtualFunctionPtr)();
void customFunction() {
std::cout << "Custom function" << std::endl;
}
int main() {
Base* basePtr = new Derived();
// 获取虚函数表指针
uintptr_t* vtablePtr = reinterpret_cast<uintptr_t*>(*reinterpret_cast<uintptr_t*>(basePtr));
// 获取虚函数表中第一个虚函数的地址
VirtualFunctionPtr originalFunction = reinterpret_cast<VirtualFunctionPtr>(vtablePtr[0]);
// 修改虚函数表中第一个虚函数的地址为 customFunction 的地址
vtablePtr[0] = reinterpret_cast<uintptr_t>(customFunction);
// 调用虚函数,此时会调用 customFunction
basePtr->virtualFunction();
// 恢复虚函数表中第一个虚函数的原始地址
vtablePtr[0] = reinterpret_cast<uintptr_t>(originalFunction);
// 再次调用虚函数,此时会调用 Derived::virtualFunction
basePtr->virtualFunction();
delete basePtr;
return 0;
}
在上述代码中:
- 通过
reinterpret_cast
操作符获取对象的虚函数表指针,并获取虚函数表中第一个虚函数的地址。 - 修改虚函数表中第一个虚函数的地址为
customFunction
的地址,从而绕过正常的虚函数调用机制。 - 调用虚函数时,会调用
customFunction
。最后恢复虚函数表中虚函数的原始地址,使虚函数调用恢复正常。这种做法依赖于特定的编译器和平台实现,可能在不同环境下无法正常工作,并且破坏了 C++ 的封装性和可维护性,因此不推荐在实际开发中使用。