• sizeof类大小 + 程序内存空间理解 + const变量的生命周期实现


    sizeof类大小 + 程序内存空间理解 + const变量的生命周期实现

    本文将通过测试C++中sizeof类大小,进一步理解进程内存空间,并理解const局部变量的生命周期不发生改变的原因。

    0. sizeof的返回值是什么?

    class A {...};
    
    • 1

    简单来说,sizeof(A)是用来获取**「编译器需要为一个类(在堆/栈上)分配空间的大小」**

    0.1 一个进程的内存空间如图所示⬇️

    注意关注.text, .data, .bss, .rodata都存储了什么内容(它们均不属于堆/栈的范畴,一般被称为静态存储区)。

    image-20220905205320876

    1. sizeof空类

    class A {
      
    };
    
    std::cout << sizeof(A);   // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这是因为空类也要进行实例化——即,即使是空类,也需要存储空间,但是由于类里面没有任何成员,编译器会对空类隐含添加一个字节(保证类在内存中有地址)。

    2. sizeof 仅包含成员函数的类

    class B {
    public: 
        void B_print() {
            cout << "B_print func" << endl;
        }
    };
    
    std::cout << sizeof(B);   // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    因为类中的成员函数是存放在代码段(.text),因此分配类B的时候,不需要为B_print()在堆上分配任何空间。

    3. sizeof 包含虚函数的类

    这个部分主要是虚函数表,多继承的虚函数表的内容,如果对下面内容云里雾里,可以看我的文章 C++ 虚函数表解析(64位版)

    3.1 单一虚函数

    class C {
    public: 
        virtual void C_print1() {
            cout << "C_print func" << endl;
        }
    };
    
    std::cout << sizeof(C);   // 8
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里sizeof(C) = 8是因为,虚函数的特点:在定义了虚函数的类中,类中需要保存一个虚函数表的首地址,因为我这里是64bit系统,因此虚函数表的首地址是8B,所以输出为8。

    3.2 多个虚函数

    class C2 {
    public: 
        virtual void C2_print1() {
            cout << "C2_print func" << endl;
        }
        virtual void C2_print2() {
            cout << "C2_print func" << endl;
        }
    };
    
    std::cout << sizeof(C2);   // 8
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    正如3.1中所说,我们只是保存了虚函数表的首地址,因此还是8。示意图如下⬇️

    image-20220905185429209

    3.3 继承

    class C3 : C {
    
    };
    
    std::cout << sizeof(C3);   // 8
    
    • 1
    • 2
    • 3
    • 4
    • 5

    继承后C3类继承了C的虚函数表,因此也是输出8。

    3.4 多重继承

    class C4 : C, C2 {
    
    };
    
    std::cout << sizeof(C4);   // 16
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里也是继承的特性,当多重继承的时候,类型C4的首地址保存的是C的虚函数表首地址,下一个地址保存的是C2的虚函数表的首地址。如图所示⬇️

    image-20220905185933635

    4. 变量(对齐)

    class D {
    public:
        uint8_t d0;
        uint32_t d1;
        uint64_t d2;
    };
    
    std::cout << sizeof(D);   // 16
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5. 析构函数和构造函数

    class E {
    public:
        E(int e){cout << "e = " << e << endl;}
        ~E(){}
    private:
        uint32_t e = 0;
    }; 
    
    std::cout << sizeof(D);   // 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里有一个变量是uint32因此输出结果是4B说明析构函数和构造函数也是不占用类的存储空间的。

    6. 静态变量

    // sizeof(F) = 1
    class F {
    public:
        static uint64_t h;
    };
    
    std::cout << sizeof(F);   // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    静态变量是存储在.data(其实这里应该是.bss)中的,也不占用类的存储空间,因此这里输出sizeof(F)是1。

    PS: 已经初始化的静态变量放在.data中;未初始化或者初始化为0的静态变量保存在.bss段中。其实.bss段和.data段是完全重叠的,也就是.bss中的数据全都是0,因此我们不需要进行存储,只需要做个标记就行。

    7. const类型变量

    // sizeof(H) = 32
    class H {
    public:
        const string str_helo = "helo";
    };
    
    std::cout << sizeof(H);   // 32
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    sizeof(H) = 32着实令人困惑:按理说,const的内容是存放在.rodata(只读数据段)中的,也就是理论上也并不会占用类的空间,但是为什么输出结果是32呢?这里和const变量的特性有关系。

    7.1 扩展:试想一下,如果我们将const 的局部变量也放在.rodata中,会发生什么?(生命周期发生变化)

    • 参考static变量放在.data / .bss 段,static的生命周期将是整个程序的生命周期,因为它并不存放在堆/栈上,而是存在静态存储区

    ​ ——也就是如果我们简单的把const的局部变量放在.rodata中,const的局部变量也会发生类似于static变量的特性,即生命周期是整个程序的生命周期,但是事实是,const仍然保留了局部变量的特性,它的生命周期相对普通局部变量并没有发生改变。

    7.2 扩展:那么编译器是怎么做到这一点(让const局部变量的生命周期不发生改变)的呢?

    其实编译器做的是:在定义的时候将它放在.rodata中,但是使用的时候是从.rodata中copy一个变量过来。其实我们真正使用的是copy过来的局部变量,因此在我们看来生命周期没有发生改变。

    回到sizeof的问题,因为编译器要对const局部变量做上述操作,因此我们在创建一个类的时候,要给cnost成员变量分配存储空间,所以sizeof(H) = 32

    8. 本文的完整例程

    8.1 源码

    #include 
    using namespace std;
    
    
    class A {
    
    };
    
    class B {
    public: 
        void B_print() {
            cout << "B_print func" << endl;
        }
    };
    
    class C {
    public: 
        virtual void C_print1() {
            cout << "C_print func" << endl;
        }
    };
    
    class C2 {
    public: 
        virtual void C2_print1() {
            cout << "C2_print func" << endl;
        }
        virtual void C2_print2() {
            cout << "C2_print func" << endl;
        }
    };
    
    class C3 : C {
    
    };
    
    class C4 : C, C2 {
    
    };
    
    class D {
    public:
        uint8_t d0;
        uint32_t d1;
        uint64_t d2;
    };
    
    // 包含构造函数和析构函数的类
    class E {
    public:
        E(int e){cout << "e = " << e << endl;}
        ~E(){}
    private:
        uint32_t e = 0;
    }; 
    
    // sizeof(F) = 1
    class F {
    public:
        static uint64_t h;
    };
    
    // sizeof(H) = 32
    class H {
    public:
        const string str_helo = "helo";
    };
    
    void test_sizeof() {
        // F::h = 1;
        cout << "sizeof(A) = " <<sizeof(A) << endl;          // 1
        cout << "sizeof(B) = " <<sizeof(B) << endl;          // 1
        cout << "sizeof(C) = " <<sizeof(C) << endl;          // 8
        cout << "sizeof(C2) = " <<sizeof(C2) << endl;        // 8
        cout << "sizeof(C3) = " <<sizeof(C3) << endl;        // 8 
        cout << "sizeof(C4) = " <<sizeof(C4) << endl;        // 16
        cout << "sizeof(D) = " <<sizeof(D) << endl;          // 16
        cout << "sizeof(E) = " <<sizeof(E) << endl;          // 4
        cout << "sizeof(F) = " <<sizeof(F) << endl;          // 1
        cout << "sizeof(H) = " <<sizeof(H) << endl;          // 32
        cout << "sizeof(H) = " <<sizeof(H) << endl;          // 32
    }
    
    
    int main() {
        test_sizeof();
        exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

    8.2 运行命令

    注意我指的是Linux或者MacOS系统下⬇️

    将8.1中代码保存在sizeof_class.cpp中。

    编译⬇️(命令行)

    g++ sizeof_class.cpp -o sizeof_class
    
    • 1

    执行⬇️(命令行)

    ./sizeof_class   
    
    • 1

    8.3 输出结果

    sizeof(A) = 1
    sizeof(B) = 1
    sizeof(C) = 8
    sizeof(C2) = 8
    sizeof(C3) = 8
    sizeof(C4) = 16
    sizeof(D) = 16
    sizeof(E) = 4
    sizeof(F) = 1
    sizeof(H) = 32
    sizeof(H) = 32
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    蔚天灿雨 2022.09.05

  • 相关阅读:
    Repetition Improves Language Model Embeddings
    浅谈AI人体姿态识别技术的先进性及安防视频监控应用场景
    简单上手Vuex
    教你如何搭建一个大学生查题公众号
    Kubernetes -- 部署k8s集群
    java StringReader类、StringWriter类
    Python 的指针,有必要理解它
    为啥不建议使用Select *
    FITC荧光素标记修饰多糖(蔗糖、麦芽糖、乳糖、淀 粉、糖原、纤维素)
    论文导读 | 支持事务与图分析的图存储系统
  • 原文地址:https://blog.csdn.net/ahundredmile/article/details/126713628