关于C++中的虚函数,两篇博客讲的很清楚,尤其是第一篇。
https://mp.weixin.qq.com/s/ZvtEL-d0-2a2QXoAGXTCCA
https://juejin.cn/post/6844903666667749389
本文主要关注如何通过对象地址去调用虚函数。
代码
1 |
|
输出:
0x7ffe5355fb78
Base::f
Base::g
Base::h
其中a=(long*)(&b)是虚表地址,long*(*a)是第一个虚函数地址。之所以需要转换为long*,是因为C++64位环境中指针占据8字节(同long,long*),这样后续的+1便能明确是将指针移动8字节,得到下一个虚函数的地址。
但是还有个疑问,上述写法(long*)*(long*)(&b)是第一个虚函数地址,+1后得到第二个虚函数地址有点奇怪,因为第一个虚函数的代码占据代码区空间不一定是8字节,为什么加1就是第二个虚函数地址呢?相反,应该说虚表的第二个元素存储第二个虚函数地址,按道理应该按照下面的写,但是下面的写法是错误的?
1 |
|
输出:
0x7ffd0e186818
Base::f
Segmentation fault (core dumped)
仔细思考下,自己想错了,这个要仔细画图去体会
从图中可以看出,long*(&b)是对象成员的首地址,也就是虚表指针的首地址,*(long*)(&b)得到对象第一个成员的值,也就是虚表的地址,还是一样,进行类型强转,因此为(long*)*(long*)(&b)。至此,虚表的首地址得到,为p=(long*)*(long*)(&b),那么+1,+2,+3为虚表后续元素的地址,*(p+n)便得到第n+1个虚函数的地址,为了进行函数调用,进行类型强转,变为函数指针,因此为(Fun)*(p+n),便得到代码1的结果。
代码2中(long*)(&b)+n得到的是第n+1个数据成员地址,而Base对象只有一个虚表指针成员,因此后续代码造成了非法内存访问,于是出现段错误。
还有个问题是如果编程时明确指定了派生类调用基类的同名函数,如p->A::func那么func的类型在编译时便能确定,调用的就是基类的函数,也就不存在动态绑定的问题了。