C++中类的内存空间大小(sizeof)分析

简介:     在C语言中存在关于结构体的存储空间大小是比较深入的话题,其中涉及计算机的基本原理、操作系统等。我认为对齐是C语言中让很多初学者都拿不准摸不透的问题,特别是在跨平台的情况下,对齐这种问题更加的复杂多变,每一种系统都有自己独特的对齐方式,在Windows中经常是以结构体重最大内置类型的存储单元的字节数作为对齐的基准,而在Linux中,所有的对齐都是以4个字节对齐。
    在C语言中存在关于结构体的存储空间大小是比较深入的话题,其中涉及计算机的基本原理、操作系统等。我认为对齐是C语言中让很多初学者都拿不准摸不透的问题,特别是在跨平台的情况下,对齐这种问题更加的复杂多变,每一种系统都有自己独特的对齐方式,在Windows中经常是以结构体重最大内置类型的存储单元的字节数作为对齐的基准,而在Linux中,所有的对齐都是以4个字节对齐。
 
    那么在C++中的类的内存空间大小又有哪些特殊的问题呢?
    首先,我认为对齐肯定也是其中的问题之一,对齐主要是为了加快读取的速度。
    关于对齐这个我认为基本上已经是操作系统内定好的,既然Linux与Windows存在差别,那么在C++的类中,关于对齐肯定也会存在一定的差别。关于对齐我认为只要记住平时使用的系统的对齐准则就可以了,即:在Windows中经常是以结构体重最大内置类型的存储单元的字节数作为对齐的基准,而在Linux中,所有的对齐都是以4个字节对齐。
   
    其次,我认为就应该讨论在基类中哪些成员占有存储空间,那些成员不占用内存空间?
    在C++中占存储区间的主要是非static的数据对象,主要包括各种内置的数据类型,类对象等,类中的函数声明以及函数定义都不算内存空间。但是需要注意所有的virtual函数(虚函数)共享一段内存区域,一般来说是4个字节。为什么只是包含非static数据对象呢?因为static数据并不属于类的任何一个对象,它是类的属性,而不是具体某一个对象的属性,在整个内存区域中只有一个内存区域存储对应的static数据,也就是所有的类对象共享这个数据,所以不能算做具体某一个对象或者类型的内存空间。
    因此可以认为基类对象的存储空间大小为:
    非static数据成员的大小 + 4 个字节(虚函数的存储空间)
    当然这个大小不是所有数据成员大小的叠加,而是存在一个对齐问题,具体的应该参考相关的对齐文章。
 
    最后,我认为肯定要关心一下派生类的存储空间了?
    在C++中,继承类是一个比较有用的类,继承使得各种类在基类的基础上扩展,这时候派生类中包含了基类的信息,一般而言,在基类中存在虚函数时,派生类中继承了基类的虚函数,因此派生类中已经继承了派生类的虚函数。因此继承类中不能再添加虚函数的存储空间(因为所有的虚函数共享一块内存区域),而仅仅需要考虑派生类中心添加进来的非static数据成员的内存空间大小。
    因此可以认为派生类对象的存储空间大小为:
    基类存储空间 + 派生类特有的非static数据成员的存储空间
 
   还有一类是比较特殊的情况,如果是虚继承的情况下,这时的存储空间大小就会发生变化。
    基类的存储空间 + 派生类特有的非static数据成员的存储空间 + 每一个类的虚函数存储空间。
  
    下面我采用一些例子说明上面的问题:
    对齐的问题:

点击(此处)折叠或打开

  1. class test
  2. {
  3. public:
  4.         test();
  5. private:
  6.         int a;
  7.         char c;
  8. };

  9. cout sizeof(test) endl;
  上面的代码在linux以及windows下都会输出8,而不是输出5,这个是在C语言中已经讨论过的话题,但是说明对齐在C++中也是要考虑的。关于操作系统的差异在后面用一个统一的例子说明。
    虚函数问题
    为了讨论虚函数,我们在test类中添加一个虚析构函数,然后再测试结果。   

点击(此处)折叠或打开

  1. class test
  2. {
  3. public:
  4.         test();
  5.         virtual ~test();
  6. private:
  7.         int a;
  8.         char c;
  9. };
  10. cout sizeof(test) endl;
这段代码与前面的代码没有什么区别,只是添加了一个虚函数,然后编译调试,这时候输出的结果12,也就是说增加了一个虚函数以后,类的数据成员增加了4个字节,那么是否是每一个虚函数都占有4个字节呢?其实是不会的,在test中加入一个新的虚函数virtual void get_a_c(),这时在输出的结果还是12,这说明所有的虚函数共享4个字节。
    static数据
    我们知道static数据是非对象的属性,而是类的属性,他不能算是某一个对象或者类型的存储空间,在类定义中只能声明,初始化只能在类外执行,当然有例外的。这也不做分析了。具体参看后面的大例子。
    派生类的存储空间
    派生类从基类中继承了很多成员,自己也会增加很多的成员,由于虚函数也会被继承下来,所以就是在派生类中不显式定义虚函数,在派生类中也会存在从基类继承下来的虚函数,因此虚函数不需要额外计算内存空间,而只需要增加基类的非static成员数据大小。定义如下面所示,该函数会输出20,刚好是添加的非static数据double d的存储空间大小。证明了上面的分析。

点击(此处)折叠或打开

  1. class test
  2. {
  3. public:
  4.         test();
  5.         virtual ~test();
  6.         virtual void get_a_c();
  7. private:
  8.         int a;
  9.         char c;
  10. };

  11. class derived_test:public test
  12. {
  13. public:
  14.         virtual ~derived_test();
  15. private:
  16.         double d ;
  17. };

  18. cout sizeof(derived_test) endl;
测试虚继承的类的大小:

点击(此处)折叠或打开

  1. class A
  2. {
  3.         char i[3];
  4. public:
  5.         virtual void a(){};
  6. };

  7. class B : public virtual A
  8. {
  9.         char j[3];
  10. public:
  11.         virtual void b(){}
  12. };

  13. class C: public virtual B
  14. {
  15.         char k[3];
  16. public:
  17.         virtual void c(){}
  18. };

  19. int main()
  20. {
  21.     cout "sizeof(A): " sizeof(A) endl;
  22.     cout "sizeof(B): " sizeof(B) endl;
  23.     cout "sizeof(C): " sizeof(C) endl;
  24.     return 0;
  25. }
下面采用一个比较综合的例子说明一下操作系统以及各种综合的影响分析。

点击(此处)折叠或打开

  1. #include iostream>
  2. #include string>
  3. #include vector>

  4. class test
  5. {
  6. public:
  7.     test();
  8.     virtual ~test();
  9.     virtual void get_a_c();
  10. private:
  11.     int a;
  12.     char c;
  13. };

  14. class derived_test:public test
  15. {
  16. public:
  17.     virtual ~derived_test();
  18. private:
  19.     double d ;
  20. };

  21. class base
  22. {
  23. private:
  24.     char a;
  25.     static int refrence_count;
  26.     std::string name;
  27.     double price;
  28.     std::vectordouble> dvec;
  29. public:
  30.     base();
  31.     virtual ~base();
  32.     static int get_count();
  33. };

  34. int base::get_count()
  35. {
  36.     return refrence_count;
  37. }

  38. int base::refrence_count = 0;

  39. class new_base
  40. {
  41. private:
  42.     char a;
  43.     double price;
  44.     std::vectordouble> dvec;
  45.     std::string name;
  46.     static int refrence_count;
  47. public:
  48.     new_base();
  49.     virtual ~new_base();
  50.     static int get_count();
  51. };

  52. int new_base::get_count()
  53. {
  54.     return refrence_count;
  55. }
  56. int new_base::refrence_count = 0;

  57. class derived: public base
  58. {
  59. private:
  60.     int min_qty;
  61.     double discount;
  62.     static int newp;
  63. public:
  64.     derived();
  65.     virtual ~derived(){};
  66. };

  67. class new_derived:public new_base
  68. {
  69. private:
  70.     double discount;
  71.     int min_pty;
  72.     static int newp;
  73. public:
  74.     new_derived();
  75.     virtual ~new_derived(){}
  76. };

  77. int main()
  78. {
  79.     std::cout "The size of test is " sizeof(test) std::endl;
  80.     std::cout "The size of derived_test is " sizeof(derived_test) std::endl;
  81.     std::cout "The size of base is " sizeof(base) std::endl;
  82.     std::cout "The size of new_base is " sizeof(new_base) std::endl;
  83.     std::cout "The size of derived is " sizeof(derived) std::endl;
  84.     std::cout "The size of new_derived is " sizeof(new_derived) std::endl;
  85.     
  86.     return 0;
  87. }
上面在windows和linux的结果分别如下:
windows:
img_c586b6815b436cb5a07c6e552a7357f3.jpg
Linux:
img_2951a31be5024668a250d1b9641e6e2d.jpg

从上面的结果可以之知道在两个系统下,结果是不一样的。说明操作系统也对类的存储空间大小有较大的影响。
 

   
目录
相关文章
|
2月前
|
Web App开发 监控 JavaScript
监控和分析 JavaScript 内存使用情况
【10月更文挑战第30天】通过使用上述的浏览器开发者工具、性能分析工具和内存泄漏检测工具,可以有效地监控和分析JavaScript内存使用情况,及时发现和解决内存泄漏、过度内存消耗等问题,从而提高JavaScript应用程序的性能和稳定性。在实际开发中,可以根据具体的需求和场景选择合适的工具和方法来进行内存监控和分析。
|
13天前
|
存储 算法 安全
基于哈希表的文件共享平台 C++ 算法实现与分析
在数字化时代,文件共享平台不可或缺。本文探讨哈希表在文件共享中的应用,包括原理、优势及C++实现。哈希表通过键值对快速访问文件元数据(如文件名、大小、位置等),查找时间复杂度为O(1),显著提升查找速度和用户体验。代码示例展示了文件上传和搜索功能,实际应用中需解决哈希冲突、动态扩容和线程安全等问题,以优化性能。
|
19天前
|
存储 缓存 编译器
【硬核】C++11并发:内存模型和原子类型
本文从C++11并发编程中的关键概念——内存模型与原子类型入手,结合详尽的代码示例,抽丝剥茧地介绍了如何实现无锁化并发的性能优化。
|
2月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
78 1
|
2月前
|
JavaScript
如何使用内存快照分析工具来分析Node.js应用的内存问题?
需要注意的是,不同的内存快照分析工具可能具有不同的功能和操作方式,在使用时需要根据具体工具的说明和特点进行灵活运用。
56 3
|
2月前
|
存储 缓存 C语言
【c++】动态内存管理
本文介绍了C++中动态内存管理的新方式——`new`和`delete`操作符,详细探讨了它们的使用方法及与C语言中`malloc`/`free`的区别。文章首先回顾了C语言中的动态内存管理,接着通过代码实例展示了`new`和`delete`的基本用法,包括对内置类型和自定义类型的动态内存分配与释放。此外,文章还深入解析了`operator new`和`operator delete`的底层实现,以及定位new表达式的应用,最后总结了`malloc`/`free`与`new`/`delete`的主要差异。
63 3
|
2月前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
183 4
|
2月前
|
Ubuntu Linux Shell
C++ 之 perf+火焰图分析与调试
【11月更文挑战第6天】在遇到一些内存异常的时候,经常这部分的代码是很难去进行分析的,最近了解到Perf这个神器,这里也展开介绍一下如何使用Perf以及如何去画火焰图。
117 5
|
2月前
|
Web App开发 JavaScript 前端开发
使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
407 9