• C++类对象所占内存空间大小分析


    前言

            类占内存空间是只类实例化后占用内存空间的大小,类本身是不会占内存空间的。用 sizeof 计算类的大小时,实际上是计算该类实例化后对象的大小。空类占用1字节原因:C++要求每个实例在内存中都有一个唯一地址,为了达到这个目的,编译器会给空类隐含添加1字节,保证空类实例化后在内存中得到的地址是独一无二的。

            在C++里空类占的存储空间是0吗?类的成员函数占存储空间吗?类的虚成员函数占存储空间吗?如果对这几个问题的回答不是很确定的话,此篇内容可供参考。

    1.空类占用1个字节的存储空间

    1. #include
    2. using namespace std;
    3. class A {
    4. };
    5. int main() {
    6. cout << "类A所占空间大小:" << sizeof(A) << "byte" << endl;
    7. // 定义两个对象a1,a2
    8. A a1;
    9. A a2;
    10. cout << "由类A实例化后的对象a1的地址:" << &a1 << endl;
    11. cout << "由类A实例化后的对象a2的地址:" << &a2 << endl;
    12. system("pause");
    13. return 0;
    14. }

    输出结果: ( 每次运行程序时系统为对象 a1、a2 分配的地址不唯一)

    原因:类中没有任何成员变量,占用的存储大小本该为0,但是如果是0,类实例化出的对象就不会在内存上占用空间,没有地址,也就无法区分这些对象。为了解决这个问题,编译器会给空类隐含加一个字节,保证用此类定义的对象都有一个独一无二的地址。 

    2.类中的普通变量占用存储空间

    1. #include
    2. using namespace std;
    3. class A {
    4. int a; // int类型变量a
    5. char p; // char类型变量p
    6. };
    7. int main() {
    8. cout << "类A所占空间大小:" << sizeof(A) << "byte" << endl;
    9. system("pause");
    10. return 0;
    11. }

    输出结果: 

     

            记得对齐的问题,这点和 struct 的对齐原则很像!int 占 4 字节,char 占 1 字节,补齐 3 字节。因此类 A 占8字节!

    3.类的成员函数(非虚函数)不占用存储空间

    在1的基础上增加类的成员函数做测试:

    1. #include
    2. using namespace std;
    3. class A {
    4. public:
    5. A(){} // 构造函数
    6. ~A(){} // 析构函数
    7. int func1(){ return 0;} // 普通成员函数
    8. int func2(){ return 0;} // 普通成员函数
    9. };
    10. int main(){
    11. cout << "类A所占空间大小:" << sizeof(A) << "byte" << endl;
    12. return 0;
    13. }

    输出结果: 

    原因:成员函数(包括构造和析构函数)编译后存放在代码区,不占用类的存储空间。

    4.类的静态成员变量不占用存储空间

    在3的基础上定义一个静态成员变量做测试:

    1. #include
    2. using namespace std;
    3. class A {
    4. public:
    5. A() {} // 构造函数
    6. ~A(){} // 析构函数
    7. int func1() { return 0; } // 普通成员函数
    8. int func2() { return 0; } // 普通成员函数
    9. private:
    10. static int num; // 类的静态成员变量
    11. };
    12. int main() {
    13. cout << "类A所占空间大小:" << sizeof(A) << "byte" << endl;
    14. system("pause");
    15. return 0;
    16. }

    输出结果: 

    原因:类里的静态成员变量在全局数据区中分配空间,不占用类的存储空间,全局只有一份,不会随着类的实例化存储在每个对象里。 

    5.类中的虚函数占用存储空间,但所占的空间不会随着虚函数的个数增长

    在4的基础上定义3个虚成员函数做测试:

    1. #include
    2. using namespace std;
    3. class A {
    4. public:
    5. A() {} // 构造函数
    6. ~A(){} // 析构函数
    7. int func1() { return 0; } // 普通成员函数
    8. int func2() { return 0; } // 普通成员函数
    9. // 3个虚函数
    10. virtual int func3() { return 0; }
    11. virtual int func4() { return 0; }
    12. virtual int func5() { return 0; }
    13. virtual int func6() { return 0; }
    14. private:
    15. static int num; // 类的静态成员变量
    16. };
    17. int main() {
    18. cout << "类A所占空间大小:" << sizeof(A) << "byte" << endl;
    19. system("pause");
    20. return 0;
    21. }

    输出结果:

    (1) x86<32位> 情况下:

    (2)x64<64位> 情况下:

    原因:C++ 类中有虚函数的时候有一个指向虚函数的指针,在 32 位系统分配指针大小为 4 字节,而在 64 位系统分配指针大小为 8 字节。无论多少个虚函数,只有这一个指针,4 字节(32位系统)或者8字节(64位系统)。注意一般的函数是没有这个指针的,而且也不占类的内存。

    6.继承 — 子类所占空间

    1. #include
    2. class CBase // 基类
    3. {
    4. public:
    5. CBase(void); // 构造函数不占空间
    6. virtual ~CBase(void); // 虚析构函数占空间,所占空间根据系统位数而定
    7. private:
    8. int a; // 普通变量占空间(4字节)
    9. char* p; // 指针类型变量占空间,根据系统位数而定
    10. };
    11. class CChild : public CBase // 子类CChild继承Cbase
    12. {
    13. public:
    14. CChild(void); // 不占空间
    15. ~CChild(void); // 不占空间
    16. virtual void test();// 父类子类共享一个虚函数指针
    17. private:
    18. int b;
    19. };
    20. int main() {
    21. char* str;
    22. std::cout << sizeof(str) << std::endl;
    23. std::cout << sizeof(CBase) << std::endl;
    24. std::cout << sizeof(CChild) << std::endl;
    25. }
    26. /*
    27. 32位系统下:
    28. 4 // 指针类型变量占4字节
    29. 12 // 虚析构函数指针占4字节+int a变量占4个字节+char* p占4个字节
    30. 16 // int类型变量b占4个字节+基类所占的12个字节
    31. // virtual void test();此时不占空间,原因是:父类子类共享一个虚函数指针
    32. */
    33. /*
    34. 64位系统下:
    35. 8 // 指针类型变量占8字节
    36. 24 // 虚析构函数指针占8字节+(int a变量占4个字节+对齐另需4字节)+(char* p占4个字节+对齐另需4字节)
    37. 32 // (int类型变量b占4个字节+对齐另需4个字节)+基类所占的24个字节
    38. */

            可见子类的大小是本身成员变量的大小加上父类的大小。其中有一部分是虚函数表的原因,父类子类共享一个虚函数指针。

    7.空类与多重继承的空类占用内存空间

    1. #include
    2. using namespace std;
    3. class A {};
    4. class A2 {};
    5. class B : public A {};
    6. class C : public A, public A2 {};
    7. class D : public virtual B {}; // 虚继承
    8. int main()
    9. {
    10. cout << sizeof(A) << endl;
    11. cout << sizeof(B) << endl;
    12. cout << sizeof(C) << endl;
    13. cout << sizeof(D) << endl;
    14. return 0;
    15. }
    16. // 32位系统输出:
    17. /*
    18. 1
    19. 1
    20. 1
    21. 4
    22. */
    23. // 64位系统输出:
    24. /*
    25. 1
    26. 1
    27. 1
    28. 8
    29. */

            空类所占内存空间为1;单一继承或多重继承空类的空类所占空间还是1;但虚继承涉及虚指针,指针大小为4(32位系统),故虚继承后空类所占空间为4(32位系统)。

     8.单一继承或多重继承时类占用内存空间

    1. #include
    2. using namespace std;
    3. class A {};
    4. class A1 {};
    5. class B : public A {
    6. int b;
    7. };
    8. class C : public A, public A1 {
    9. int c;
    10. };
    11. int main()
    12. {
    13. cout << sizeof(A) << endl;
    14. cout << sizeof(B) << endl;
    15. cout << sizeof(C) << endl;
    16. return 0;
    17. }
    18. // 32位系统输出:
    19. /*
    20. 1
    21. 4
    22. 8
    23. */
    24. // 64位系统输出:
    25. /*
    26. 1
    27. 4
    28. 8
    29. */

    9.共有继承

    1. #include
    2. class A {
    3. };
    4. class A1 : public A {
    5. };
    6. class B : public A{
    7. virtual void fun() = 0; // 定义虚函数
    8. };
    9. // 共有继承,共用虚函数指针,没有虚基指针
    10. class C : public B{
    11. };
    12. class D : public A, public B{
    13. };
    14. int main()
    15. {
    16. std::cout << "sizeof(A):" << sizeof(A) << std::endl;
    17. std::cout << "sizeof(A1):" << sizeof(A1) << std::endl;
    18. std::cout << "sizeof(B):" << sizeof(B) << std::endl;
    19. std::cout << "sizeof(C):" << sizeof(C) << std::endl;
    20. std::cout << "sizeof(D):" << sizeof(D) << std::endl;
    21. return 0;
    22. }
    23. /*
    24. 32位系统下输出:
    25. sizeof(A):1 // 空类A(1)
    26. sizeof(A1):1 // 空类A(0) + A1(1)
    27. sizeof(B):4 // 空类A(0) + 虚函数指针(4)
    28. sizeof(C):4 // 与B共用虚函数指针(4)
    29. sizeof(D):8 // A(1+3<对齐>) + 与B共用虚函数指针(4)
    30. */
    31. /*
    32. 64位系统下输出:
    33. sizeof(A):1 // 空类A(1)
    34. sizeof(A1):1 // 空类A(0) + A1(1)
    35. sizeof(B):8 // 空类A(0) + 虚函数指针(8)
    36. sizeof(C):8 // 与B共用虚函数指针(8)
    37. sizeof(D):16 // A(1+7<对齐>) + 与B共用虚函数指针(8)
    38. */

    共有继承,共用虚函数指针,没有虚基指针。 

    10.虚继承

    1. #include
    2. /*
    3. 虚继承与继承的区别:
    4. 1.多了一个虚基指针
    5. 2.虚基类位于派生类存储空间的最末尾
    6. 3.不会共用虚函数指针
    7. */
    8. class A
    9. {
    10. char a[3];
    11. public:
    12. virtual void fun1() {};
    13. };
    14. // 测试一:单个虚继承,不带虚函数
    15. class B : public virtual A
    16. {
    17. char b[3];
    18. };
    19. // 测试二:单个虚继承,带自己的虚函数
    20. class C : public virtual A
    21. {
    22. char c[3];
    23. public:
    24. virtual void fun2() {};
    25. };
    26. // 测试三:双重继承
    27. class D : public virtual C
    28. {
    29. char d[3];
    30. public:
    31. virtual void fun3() {};
    32. };
    33. int main()
    34. {
    35. std::cout << sizeof(A) << std::endl;
    36. std::cout << sizeof(B) << std::endl;
    37. std::cout << sizeof(C) << std::endl;
    38. std::cout << sizeof(D) << std::endl;
    39. return 0;
    40. }
    41. /*
    42. 32位系统输出:
    43. 8 // 8【虚函数指针占4个字节;char a[3]占3个字节,跟虚函数指针所占空间对齐需要另外加1个】
    44. 16 // 8(A) + 8(B)【8 == (3+1)+虚基指针】
    45. 20 // 8(A) + 12(C)【12 == (3+1)+自己的虚函数指针+虚基指针】
    46. 32 // (char d[3]占3个字节+对齐另需1个字节)+类D自己的虚函数指针(4个字节)+虚基指针(4个字节)
    47. +(char c[3]占3个字节+对齐另需1个字节)+类C自己的虚函数指针(4个字节)+虚基指针(4个字节)
    48. +(char a[3]占3个字节,跟虚函数指针所占空间对齐需要另外加1个字节) + 类A自己的虚函数指针占4个字节
    49. */
    50. /*
    51. 64位系统输出:
    52. 16 // 【虚函数指针占8个字节;char a[3]占3个字节,跟虚函数指针所占空间对齐需要另外加5个】
    53. 32 // 16(A) + 16【16 == (char b[3]占3个字节+对齐另需5个字节)+虚基指针(8个字节)】
    54. 40 // 16(A) + 24【24 == <(char c[3]占3个字节+对齐另需5个字节)+自己的虚函数指针(8个字节)+虚基指针(8个字节)>】
    55. 64 // (char d[3]占3个字节+对齐另需5个字节)+类D自己的虚函数指针(8个字节)+虚基指针(8个字节)
    56. +(char c[3]占3个字节+对齐另需5个字节)+类C自己的虚函数指针(8个字节)+虚基指针(8个字节)
    57. +(char a[3]占3个字节,跟虚函数指针所占空间对齐需要另外加5个字节) + 类A自己的虚函数指针占8个字节
    58. */

            注意,虚继承的时候 A B C D 四个类不仅不会共享虚基类指针,也不会共享虚函数指针,要和普通继承区分开来。

    具体分析如下:

    1. class A size(8):
    2. +---
    3. 0 | {vfptr}
    4. 4 | a
    5. | (size=1)
    6. 8 +---
    7. class B size(16):
    8. +---
    9. 0 | {vfptr}
    10. 4 | {vbptr}
    11. 8 | b
    12. | (size=1)
    13. +---
    14. +--- (virtual base A)
    15. 12 | a
    16. | (size=1)
    17. 16 +---
    18. class C size(20):
    19. +---
    20. 0 | {vfptr}
    21. 4 | {vbptr}
    22. 8 | b
    23. | (size=1)
    24. +---
    25. +--- (virtual base A)
    26. 12 | {vfptr}
    27. 16 | a
    28. | (size=1)
    29. 20 +---
    30. class D size(32):
    31. +---
    32. 0 | {vfptr}
    33. 4 | {vbptr}
    34. 8 | c
    35. | (size=1)
    36. +---
    37. +--- (virtual base A)
    38. 12 | {vfptr}
    39. 16 | a
    40. | (size=1)
    41. +---
    42. +--- (virtual base B)
    43. 20 | {vfptr}
    44. 24 | {vbptr}
    45. 28 | b
    46. | (size=1)
    47. 32 +---
    • 虚表(vftable
    • 虚函数指针(vfptr
    • 虚基指针(vbptr

    11.总结

            空的类是会占用内存空间的,而且大小是 1,原因是 C++ 要求每个实例(对象)在内存中都有独一无二的地址。

    (一)类内部的成员变量:

    • 普通的变量:是要占用内存的,但是要注意对齐原则(这点和 struct 类型很相似)。
    • static 修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。

    (二)类内部的成员函数:

    • 普通函数:不占用内存。
    • 虚函数:有一个指向虚函数的指针,要占用 4 个字节或 8 个字节(根据系统位数来定),用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的。

    (三)虚继承与继承的区别:

    • 多了一个虚基指针。
    • 虚基类位于派生类存储空间的最末尾。
    • 不会共用虚函数指针。

    参考自:

    C++类对象到底占多大存储空间呢_类的成员函数占用空间吗_haowunanhai的博客-CSDN博客
    https://www.cnblogs.com/linuxAndMcu/p/10388330.html

    C++中的类所占内存空间总结

    c++虚表(vftable)、虚函数指针(vfptr)、虚基指针(vbptr)的测试结果

    https://www.cnblogs.com/aqing1987/p/4210773.html 

  • 相关阅读:
    JS迭代器及异步
    【2023年11月第四版教材】第17章《干系人管理》(合集篇)
    50、Spring WebFlux 的 自动配置 的一些介绍,与 Spring MVC 的一些对比
    达梦数据库MAIN表空间导致磁盘满问题的处理和总结
    PLINK相关性分析,分类变量和连续型变量
    IEDA代码模板
    [激光原理与应用-28]:《激光原理与技术》-14- 激光产生技术 - 激光的主要参数与指标
    当我们谈论量化时,我们在谈论什么?量化投资常见策略有哪些?| 融券T0和高频交易详解【邢不行】
    04 多表查询
    jsp196ssm毕业设计选题管理系统hsg4361B6
  • 原文地址:https://blog.csdn.net/m0_48241022/article/details/133936526