面试题答案
一键面试1. 非虚函数在通过基类指针调用时不会表现出多态性的原因(内存布局层面)
- 内存布局:在C++中,类对象的内存布局是按照声明顺序排列成员变量的。对于
Base
类,其对象的内存布局简单地包含baseData
成员变量和func
函数的代码入口地址(函数本身并不占用对象的内存空间,对象中存储的是函数指针或直接是函数入口地址,取决于具体实现)。当Derived
类继承自Base
类时,Derived
类对象的内存布局首先是Base
类部分(包含baseData
),接着是Derived
类自己的derivedData
成员变量。 - 调用机制:非虚函数在编译时就确定了调用的具体函数。当通过基类指针
Base* ptr = new Derived();
调用非虚函数ptr->func();
时,编译器根据指针的静态类型(即Base
类型)直接确定要调用的函数是Base::func
。这是因为编译器在编译阶段就知道Base
类中func
函数的地址,不需要在运行时动态查找。相比之下,虚函数的调用依赖于虚函数表(vtable)。虚函数表是在运行时根据对象的实际类型来确定的,而对于非虚函数,没有这种运行时动态绑定的机制,所以不会表现出多态性。
2. 频繁调用非虚函数场景下编译器的优化方式
- 内联优化:编译器可以将非虚函数的代码直接嵌入到调用处,避免函数调用的开销(如栈帧的创建与销毁、参数传递等)。对于简单的非虚函数,编译器通常会自动进行内联优化。在上述代码中,如果
func
函数中的操作比较简单,编译器可能会将func
函数内联,使得调用func
的地方直接替换为func
函数的代码,提高执行效率。可以使用inline
关键字显式提示编译器进行内联,但最终是否内联还是由编译器决定。 - 寄存器优化:编译器可以将频繁使用的变量(如
baseData
)存储在寄存器中,减少内存访问次数。在func
函数中,如果baseData
被频繁访问,编译器可能会将其加载到寄存器中,使得对baseData
的操作直接在寄存器中进行,提高访问速度。 - 循环展开:如果
func
函数内部包含循环,编译器可以进行循环展开优化。即将循环体展开多次,减少循环控制语句的开销,提高指令级并行度。例如,原本多次迭代的循环可以展开为顺序执行的代码块,减少了每次迭代时的条件判断和跳转操作。