当前位置:   article > 正文

C++冷知识(四)——类对象及虚函数表的内存空间排布_c++虚函数表所占空间

c++虚函数表所占空间

讲正题之前先复习一下指针的占用空间大小。指针的占用空间与编译器位数有关,当编译器为64位时,指针占用空间为8字节;当编译器为32位时,指针占用空间为4字节。

其实指针也就是地址,保存一个指针的占用空间与一个地址的大小是一样的。

1、普通类(即没有虚基类和虚函数)的对象中,其内存中保存的数据即为基类的非静态数据成员与当前类的非静态数据成员 及 内存对齐所占用的额外空间。

2、重点来了,对于存在虚基类和虚函数的类,其对象会自动多一个非静态数据成员(该数据成员是一个指针,指针内容保存虚函数表的地址),而且该非静态数据成员位于最前面,即所谓的“第一个成员”。效果如下图:

原声明如下:

  1. struct A
  2. {
  3. int value;
  4. virtual void foo() { }
  5. };

这个A的非静态数据成员将会是如下图所示:

  1. struct A
  2. {
  3. void *virtual_table;
  4. int value;
  5. };

假设一个地址是用8字节表示的,则从该对象的起始地址开始,到接下来的7个字节,这8个字节保存的是虚函数表的地址,假设一个对象名称为object,则可以通过 * reinterpret_cast<unsigned long long *>(&a)取得该8字节的内容,所得内容即为虚函数表的地址。那么对象的内存空间如下图:

对于虚函数表的内存空间,则按照虚函数的声明顺序向下排列,如下图代码:

  1. class A
  2. {
  3. virtual void func1() { }
  4. virtual void func2() { }
  5. virtual void func3() { }
  6. virtual ~A() { }
  7. };

其虚函数表空间如下图:

下面代码将演示如何获取类的虚函数地址方法:

  1. #include <iostream>
  2. //用于获取能保存一个指针大小的基本数据类型
  3. template<std::size_t>
  4. struct AddressSize
  5. {
  6. typedef int type;// 指针占用4字节
  7. };
  8. // 模板特化
  9. template<>
  10. struct AddressSize<8>
  11. {
  12. typedef unsigned long long type;// 指针占用8字节
  13. };
  14. class A
  15. {
  16. public:
  17. virtual void func1()
  18. {
  19. std::cout << "this is func1" << std::endl;
  20. }
  21. virtual void func2(double)
  22. {
  23. std::cout << "this is func2" << std::endl;
  24. }
  25. virtual void func3(int)
  26. {
  27. std::cout << "this is func3" << std::endl;
  28. }
  29. virtual ~A()
  30. {
  31. std::cout << "this is ~A()" << std::endl;
  32. }
  33. };
  34. int main()
  35. {
  36. A a;
  37. typedef AddressSize<sizeof(void *)>::type pointer_size_type;//获取能代表一个指针大小的基本数据类型
  38. typedef void(*func1)();
  39. typedef void(*func2)(double);
  40. typedef void(*func3)(int);
  41. //获取对象的起始地址
  42. pointer_size_type *address = reinterpret_cast<pointer_size_type *>(&a);
  43. //得到虚函数表地址的数值
  44. pointer_size_type virtual_func_table_address = *address;
  45. //获得虚函数表地址数值转换为指针
  46. pointer_size_type *virtual_func_list_address = reinterpret_cast<pointer_size_type *>(virtual_func_table_address);
  47. //获取func1函数的地址,因为func1函数是第一个声明的函数,所以加0
  48. pointer_size_type func1_address = *(virtual_func_list_address + 0);
  49. func1 f1 = reinterpret_cast<func1>(func1_address);
  50. f1();
  51. //获取func2函数的地址,因为func2函数是第二个声明的函数,所以加1
  52. pointer_size_type func2_address = *(virtual_func_list_address + 1);
  53. func2 f2 = reinterpret_cast<func2>(func2_address);
  54. f2(0.0);
  55. //获取func3函数的地址,因为func3函数是第一个声明的函数,所以加2
  56. pointer_size_type func3_address = *(virtual_func_list_address + 2);
  57. func3 f3 = reinterpret_cast<func3>(func3_address);
  58. f3(1);
  59. std::cout << "finish" << std::endl;
  60. return 0;
  61. }

运行结果如图:

 

本次内容结束,如有问题,欢迎指出!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/244121
推荐阅读
相关标签
  

闽ICP备14008679号