代码改变世界

C++中的虚函数表是什么时期建立的

2021-11-13 08:59  dreamboy2000  阅读(446)  评论(0编辑  收藏  举报

虚函数表是在什么时期建立的?

  最近参加阿里巴巴公司的内推,面试官问了“虚函数表是在什么时期建立的?”。因为以前对虚函数表的理解不够多,所以就根据程序构建(Build)的四个过程(预编译、编译、汇编和链接),推导出虚函数表应该是在编译期确定的,原因如下:

  1)预编译器主要处理那些源代码文件中的以“#”开始的预编译指令,如“#include”、“#define”。很明显这个过程可以排除。

  2)汇编器是将编译器生成的汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。汇编过程相对于编译期来说比较简单,没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译就行了。所以,汇编期也是可以排除的。

  3)链接器(现只考虑静态链接)是将汇编器生成的目标文件(和库)链接成一个可执行文件,本质上做的是重定位(Relocation)的工作,详细可参考《程序员的自我修养》2.3、2.4节。很明显链接期也是可以排除的。

  4)编译器要做的事情就比较多了,包括词法分析、语法分析、语义分析及优化代码等,是整个程序构建的核心。所以,排除了预编译期、汇编期、链接期及考虑到编译期所做的事情,虚函数表应该是在编译期建立的。

  

  上边给出的答案还是有点不够全面,因为忽略了动态链接。不过,我们在《深度探索C++对象模型》的4.2节能够找到完美答案,具体摘抄如下:

  “表格中的virtual functions地址是如何被建构起来的?在C++中,virtual functions(可经由其class object被调用)可以在编译时期获知。此外,这一组地址是固定不变的,执行期不可能新增或替换之。由于程序执行时,表格的大小和内容都不会改变,所以其建构和存取皆可以由编译器完全掌控,不需要执行期的任何介入。

虚函数表

   C++中的虚函数的作用主要是实现了多态机制,即父类类别的指针(或者引用)指向其子类的实例,然后通过父类的指针(或者引用)调用实际子类的成员函数。多态机制可以简单地概括为“一个接口,多种方法”。

  虚函数是通过一张虚函数表(Virtual Table)来实现的,简称为V-Table。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得极为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

  下边我们通过一个小程序来看看虚函数表到底是怎么样的?

复制代码
 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Base
 5 {
 6 public:
 7     virtual void fn() { cout << "In Base class" << endl; }
 8 };
 9 class Sub :public Base
10 {
11 public:
12     virtual void fn() { cout << "In Sub class" << endl; }
13 };
14 
15 void main()
16 {
17     Base bc;
18     Sub sc;
19 }
复制代码

   对这个程序调试,截图如下:

  

  由上图可知,虚函数表_vfptr已经将父类Base和子类Sub的相同函数fn动态绑定到对应的类上,而且地址不一样。

有关多态的值得注意的例子 

不用多态

  先看第一个程序:

复制代码
 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Base
 5 {
 6 public:
 7     void fn() { cout << "In Base class" << endl; }
 8 };
 9 class Sub :public Base
10 {
11 public:
12     void fn() { cout << "In Sub class" << endl; }
13 };
14 
15 void test(Base& b)
16 {
17     b.fn();
18 }
19 
20 void main()
21 {
22     Base bc;
23     Sub sc;
24     test(bc);
25     test(sc);
26 }
复制代码

  函数输出如下:

  

  Sub对象sc在传递给test函数时,其另外添加的方法成员等(Sub)会被截掉,只剩Base部分,所以输出是In Base class而不是In Sub class。

利用多态

  具体程序如下:

复制代码
 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Base
 5 {
 6 public:
 7     virtual void fn() { cout << "In Base class" << endl; }
 8 };
 9 class Sub :public Base
10 {
11 public:
12     virtual void fn() { cout << "In Sub class" << endl; }
13 };
14 
15 void test(Base& b)
16 {
17     b.fn();
18 }
19 
20 void main()
21 {
22     Base bc;
23     Sub sc;
24     test(bc);
25     test(sc);
26 }
复制代码

  程序输出如下:

  

  这下程序就正确了。

 

  其实还有另一种方法可以达成跟虚函数一样的效果,不过这并不是一种好的做法:

复制代码
 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Base
 5 {
 6 public:
 7     void fn() { cout << "In Base class" << endl; }
 8 };
 9 class Sub :public Base
10 {
11 public:
12     void fn() { cout << "In Sub class" << endl; }
13 };
14 
15 void main()
16 {
17     Base bc;
18     Sub sc;
19     bc.fn();
20     sc.fn();
21 }
复制代码

  具体输出如下:

  

参考资料

  《深度探索C++对象模型》 

  《程序员的自我修养》

 

分类: C/C++基础
<div id="blog_post_info">
7
1
<div class="clear"></div>
<div id="post_next_prev">

<a href="https://www.cnblogs.com/xiehongfeng100/p/4671958.html" class="p_n_p_prefix">« </a> 上一篇:    <a href="https://www.cnblogs.com/xiehongfeng100/p/4671958.html" title="发布于 2015-07-23 22:17">函数指针和回调函数</a>
<br>
<a href="https://www.cnblogs.com/xiehongfeng100/p/4684825.html" class="p_n_p_prefix">» </a> 下一篇:    <a href="https://www.cnblogs.com/xiehongfeng100/p/4684825.html" title="发布于 2015-07-29 00:55">指针和数组解析</a>