(偷懒就直接截PPT了)
是上学期,老师叫我们回去找一个关于随机加载的问题,然后我就想知道对象里面到底有哪些是会被随机加载的,然后我就去看了一下对象在内存中是怎么分布的,发现在堆区好像只有虚表指针和成员变量,想起来以前对虚表一直不是很了解只是知道它在多态方面起着很重要的作用,就去网上搜了一下。
首先是虚表的结构,它由一个放在对象头部的虚表指针指向,然后在虚函数表里放的是该类的虚函数地址?我不知道这样是否准确,那个地址里是一个跳转到函数的指令。这个是没有继承的情况下的虚表结构。
在VS监视窗口里查看可以看到Ba指针指向的对象开始是一个虚表指针,指向的地方就放了两个该类的虚函数。
(地址不同是因为放图片放错了,道理是这个道理)
还有这个是它在内存中的分布情况。
现在我们来看一下单继承,有重写父类虚函数的情况。考虑这样一个继承。
首先我们看VS监视窗口里的信息,发现对象最开始仍是一个虚表指针,不过这个时候虚表指针的第一个元素已经不是父类的虚函数入口地址了,变成了之类的重写函数的入口地址。但是在这儿我没有发现之类的虚函数的信息。
查看虚函数表处的内存信息,发现前第三个元素并不是补齐的0字节,也不是字符串信息,在反汇编窗口查看这个地址发现正好是子类自己的虚函数的入口地址,所以之类的虚函数应该是紧跟在父类的虚函数后边。
结构就应该是这个样子。
我看到网上有人讲多态简而言之就是用父类型的指针指向子类的示例,然后通过调用父类的指针调用实际之类的成员函数。查看代码的汇编的话,发现这两种情况生成的汇编代码一模一样,所以调用的函数的地址并不是在编译期就确定的,而是在运行时在各自虚函数表里去查。
再看一下多继承下的情况,考虑这样一个继承关系。
监视窗口里的信息表示这次这个对象拥有两个虚表指针,两个虚表里的f()函数入口地址都被覆盖过了,那子类的虚函数呢,跟在A虚表后面还是B后面还是都有?
查看一下虚表的内存信息,发现是跟在第一个虚表后面。
结构就应该是这个样子。
我在想为什么要这么设计的时候又遇到了另一个问题,我在看第二个父类指针和子类指针调用函数的汇编代码时发现两处的代码和单继承不同,它们居然不一样,第二个父类指针在取虚表地址时,没有加上第二个虚表指针在对象中的偏移?
是因为编译器在把子类指针赋值给父类时,如果这个父类不是继承的第一个父类的话会把指针的数值作修改使其指向自己的虚表指针,这样的话就可以消除前面的父类造成的影响,才可以通过这个汇编代码实现多态。
还有一个问题,两个虚表里面存子类重写的虚函数的地址不同,尽管它们最后跳转到同一个地方,不知道这儿为什么要这么设计。
菱形继承唉没什么好讲的,就相当于两个单继承一个多继承。
知道了这些我们可以做一些相当没有意义的事,比如尽管Ba指向的对象的虚表里面有子类的fb()函数的指针,但是编译器并不允许这样做,既然函数入口地址存在,那么我们其实也可以通过这样的方式访问:声明一个对应要访问函数的函数指针,然后直接在虚函数表里获取地址进行调用。同样的道理也可以用来访问non-public的虚函数。