请选择 进入手机版 | 继续访问电脑版

[C 语言] C++ typeid 和虚函数详解

[复制链接]
查看155 | 回复19 | 2021-9-12 14:48:51 | 显示全部楼层 |阅读模式
目次

typeid 和虚函数

前面咱们讲到 typeid 的操作返回值是 type_info 对象的引用,然后输出返回值的地址是雷同 的,测试代码如下:

  1. #include <iostream>
  2. #include <functional>
  3. using namespace std;
  4. class Base{
  5. public:
  6. virtual
  7. void test(){
  8. cout << "Base::test" << endl;
  9. }
  10. };
  11. class Derived : public Base{
  12. public:
  13. void test(){
  14. cout << "Derived::test" << endl;
  15. }
  16. virtual
  17. ~Derived(){
  18. cout << "Derived::~Derived" << endl;
  19. }
  20. };
  21. int main()
  22. {
  23. Base* pBase = new Base();
  24. Base* pBase2 = new Derived();
  25. Derived* pDerive = new Derived();
  26. //typeid(pBase2) 和 typeid(pDerive) 返回地址相同
  27. cout << "typeid(pBase2) = " << &typeid(*pBase2) << " typeid(pDerive) = "<< &typeid(*pDerive) << endl;
  28. return 0;
  29. }
复制代码
  1. output信息:
  2. typeid(pBase2) = 0x55dd724c6d48 typeid(pDerive) = 0x55dd724c6d48
复制代码

也就是说,0x55dd724c6d48 就是 Derived 类编译之后的类标识(type_info)数据信息!是否真的云云 ,咱们可以添加一下代码测试:

  1. int main()
  2. {
  3. Base* pBase = new Base();
  4. Base* pBase2 = new Derived();
  5. Derived* pDerive = new Derived();
  6. //typeid(pBase2) 和 typeid(pDerive) 返回地址相同
  7. cout << "typeid(pBase2) = " << &typeid(*pBase2) << " typeid(pDerive) = "<< &typeid(*pDerive) << endl;
  8. //class Base type_info 地址
  9. cout << "typeid(Base) = " << &typeid(Base) << endl;
  10. //class Derive type_info 地址
  11. cout << "typeid(Derived) = " << &typeid(Derived) << endl;
  12. //指针类型推导
  13. cout << "point ---- typeid(pBase2) = " << &typeid(pBase2) << " typeid(pDerive) = "<< &typeid(pDerive) << endl;
  14. return 0;
  15. }
复制代码
  1. ouput信息:
  2. typeid(pBase2) = 0x562309345d48 typeid(pDerive) = 0x562309345d48
  3. typeid(Base) = 0x562309345d60
  4. typeid(Derived) = 0x562309345d48
  5. point ---- typeid(pBase2) = 0x562309345d28 typeid(pDerive) = 0x562309345d08
复制代码

可以看到,Derived 类的 type_info 信息的地址就是 0x558a4dec7d48 !要留意 的一点:直接对指针范例 举行 操作,并不能返回精确 的原始范例 。

好了嘛,那 typeid 到底是咋从虚函数表找到这个地址的呢?假如 大家看过我之前的 深入明白 new[]和delete[]_master-计算机科学专栏-CSDN博客 一文,应该就可以或许 想到是不是C++编译器对虚函数表举行 构造的过程中是不是也一样,做了地址偏移呢?

咱们看看上面代码的汇编信息:

C++ typeid 和虚函数详解

通过查看汇编信息,我们得到以下结论:

虚函数表中确实存有typeinfo信息(第一个虚函数的地址偏移 -1 即是)typeinfo信息是区分指针范例 是的(指针范例 有前缀P,比方 P4Base、P7Derived)

然后,我们细致 观察四个 typeinfo 类(Derived*、 Base*、Derived、Base),每个typeinfo 类都有一个虚函数表,继承自 vtable for __cxxabiv1::******* ,后面的信息会不一样。这里对该信息做一下简单阐明 :

  1. 对于启用了 RTTI 的类来说会继承 __cxxabiv1 里的某个类所有的基础类(没有父类的类)都继承于_class_type_info所有的基础类指针都继承自 __pointer_type_info所有的单一继承类都继承自 __si_class_type_info所有的多继承类都继承自 __vmi_class_type_info
  2. <p style="text-align: left">以typeinfo for Derived为例:
  3. <p style="text-align: center"><img alt="" src="https://img.jbzj.com/file_images/article/202109/2021090815123653.png" />
  4. 然后是指向存储类型名字的指针,
  5. 如果有继承关系,则最后是指向父类的 typeinfo 的记录。
复制代码

以是 ,假如 是正常调 typeinfo 基类(_class_type_info、__pointer_type_info、__si_class_type_info、__vmi_class_type_info)的方法,应该会动态调到 type_info 的继承类 (typeinfo for Derived*、typeinfo for Base*、typeinfo for Derived、typeinfo for Base)的方法。

但是,typeid 操作指针范例 时并不是如许 ,阐明 C++编译器底层有特别 处理!

调试以下代码:

  1. cout << typeid(*pBase2).name();
  2. cout << typeid(*pDerive).name();
  3. cout << typeid(pBase2).name();
  4. cout << typeid(pDerive).name();
复制代码

通过汇编信息,可以看到这里并没有做任何动态调用的逻辑,而是直接返回该指针范例 的typeinfo信息,这也就表明 了为什么 typeid 操作指针和操尴尬刁难 象的结果 不一样!

C++ typeid 和虚函数详解

那么我们在利用 typeid时,假如 要获取到真实对象范例 ,应该要将指针去掉!

为了验证我们前面的结论: 虚函数表中确实存有typeinfo信息(第一个虚函数的地址偏移 -1 即是),咱们可以直接通过指针的方式操作虚函数表!

测试代码如下:

  1. #include <iostream>
  2. #include <functional>
  3. using namespace std;
  4. class Base{
  5. public:
  6. virtual
  7. void test(){
  8. cout << "Base::test" << endl;
  9. }
  10. };
  11. class Derived : public Base{
  12. public:
  13. void test(){
  14. cout << "Derived::test" << endl;
  15. }
  16. virtual
  17. ~Derived(){
  18. cout << "Derived::~Derived" << endl;
  19. }
  20. };
  21. typedef void (*FUNPTR)();
  22. type_info* getTypeInfo(unsigned long ** vtbl){
  23. type_info* typeinfo = (type_info*)((unsigned long)vtbl[-1]);
  24. return typeinfo;
  25. }
  26. void visitVtbl(unsigned long ** vtbl, int count)
  27. {
  28. cout << vtbl << endl;
  29. cout << "\t[-1]: " << (unsigned long)vtbl[-1] << endl;
  30. typedef void (*FUNPTR)();
  31. for (int i = 0; vtbl[i] && i < count; ++i)
  32. {
  33. cout << "\t[" << i << "]: " << vtbl[i] << " -> ";
  34. FUNPTR func = (FUNPTR)vtbl[i];
  35. func();
  36. }
  37. }
  38. int main()
  39. {
  40. Base* pBase = new Base();
  41. Base* pBase2 = new Derived();
  42. Derived* pDerive = new Derived();
  43. //这里去遍历虚函数表
  44. visitVtbl((unsigned long **)*(unsigned long **)pBase2, 2);
  45. //获取虚函数表-1位置的typeinfo地址
  46. cout << "pDerive = " << getTypeInfo((unsigned long **)*(unsigned long **)pDerive) << " "
  47. << getTypeInfo((unsigned long **)*(unsigned long **)pDerive)->name() << endl;
  48. //获取虚函数表-1位置的typeinfo地址
  49. cout << "pBase2 = " << getTypeInfo((unsigned long **)*(unsigned long **)pBase2) << " "
  50. << getTypeInfo((unsigned long **)*(unsigned long **)pBase2)->name() << endl;
  51. return 0;
  52. }
复制代码

这里要留意 的一点是,遍历虚函数表 visitVtbl 方法的第2个参数,本身 控制不要越界,此外,还要留意 调用的次序 ,假如 先调用了虚析构函数,会导致内存错误!

  1. output信息:
  2. 0x5620022edd10
  3. [-1]: 94695475567936
  4. [0]: 0x5620022eb5fa -> Derived::test
  5. [1]: 0x5620022eb636 -> Derived::~Derived
  6. pDerive = 0x5620022edd40 7Derived
  7. pBase2 = 0x5620022edd40 7Derived
复制代码

通过直接访问虚函数表-1位置,我们可以看到输出的日志 信息与我们前面的结论是同等 的!也即是C++编译器给我们做了偏移操作(在-1的位置存储了type_info信息,实例化对象中的虚函数表地址是偏移之后的地址)。

总结

本篇文章就到这里了,渴望 可以或许 给你带来帮助,也渴望 您可以或许 多多关注脚本之家的更多内容!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

avatar 李志敏 | 2021-9-14 13:54:20 | 显示全部楼层
好东西,学习学习!
回复

使用道具 举报

avatar 慧眼识英雄1 | 2021-9-17 06:57:24 | 显示全部楼层
顶顶更健康!
回复

使用道具 举报

avatar 无为307 | 2021-9-19 23:14:18 | 显示全部楼层
祖国尚未统一,我却天天灌水,好内疚!
回复

使用道具 举报

avatar wpwexx127388 | 2021-9-20 10:52:35 | 显示全部楼层
顶顶更健康!
回复

使用道具 举报

avatar 念佳泽 | 2021-9-21 18:37:55 | 显示全部楼层
admin楼主是在找骂么?
回复

使用道具 举报

avatar 123457660 | 2021-9-23 21:00:02 | 显示全部楼层
admin楼主,我告诉你一个你不知道的的秘密,有一个牛逼的网站,运动刷步数还是免费刷的,QQ和微信都可以刷,特别好用。访问地址:http://yd.mxswl.com 猫先森网络
回复

使用道具 举报

你觉得该怎么做呢?
回复

使用道具 举报

不灌水就活不下去了啊!
回复

使用道具 举报

精华帖的节奏啊!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则