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

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

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

0. sizeof的返回值是什么?

class A {...};

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

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

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

image-20220905205320876

1. sizeof空类

class A {
  
};

std::cout << sizeof(A);   // 1

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

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

class B {
public: 
    void B_print() {
        cout << "B_print func" << endl;
    }
};

std::cout << sizeof(B);   // 1

因为类中的成员函数是存放在代码段(.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

这里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

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

image-20220905185429209

3.3 继承

class C3 : C {

};

std::cout << sizeof(C3);   // 8

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

3.4 多重继承

class C4 : C, C2 {

};

std::cout << sizeof(C4);   // 16

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

image-20220905185933635

4. 变量(对齐)

class D {
public:
    uint8_t d0;
    uint32_t d1;
    uint64_t d2;
};

std::cout << sizeof(D);   // 16

5. 析构函数和构造函数

class E {
public:
    E(int e){cout << "e = " << e << endl;}
    ~E(){}
private:
    uint32_t e = 0;
}; 

std::cout << sizeof(D);   // 4

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

6. 静态变量

// sizeof(F) = 1
class F {
public:
    static uint64_t h;
};

std::cout << sizeof(F);   // 1

静态变量是存储在.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

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 <bits/stdc++.h>
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);
}

8.2 运行命令

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

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

编译⬇️(命令行)

g++ sizeof_class.cpp -o sizeof_class

执行⬇️(命令行)

./sizeof_class   

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

蔚天灿雨 2022.09.05

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值