跳转至

存储区, 作用域与生命周期(存储期)

约 1177 个字 预计阅读时间 4 分钟

内存的存储区

不同 OS/ABI/编译器会有差异,但典型用户态进程的虚拟地址空间可抽象为:

  1. 代码段(text / .text) 存放机器指令(函数体)。 通常只读+可执行。

  2. 只读数据段(rodata / .rodata) 字符串字面量、const 只读常量等可能放这里(实现决定)。 通常只读不可写。

  3. 已初始化的全局/静态数据段(data / .data) 有显式非零初始化的全局变量、静态变量、静态成员等。

  4. 未初始化或零初始化的全局/静态数据段(bss / .bss) 未显式初始化或初始化为 0 的全局/静态对象(语言层面会做零初始化)。

  5. 堆(heap / free store) new/delete、malloc/free 等动态分配区域(一般是“自由存储区”,常映射到堆实现)。 分配/释放由运行时/分配器管理。

  6. 栈(stack)(每进程程一份) 调用栈帧:返回地址、保存寄存器、函数参数、自动存储期局部变量等(具体布局实现相关)。 进入作用域“压栈”,离开作用域“出栈”。

  7. 内存映射区(mmap / 映射段)(OS 概念) 动态库、文件映射、匿名映射、大块分配等常在此区域。 对于linux系统, 线程栈也会在父进程的内存映射区。

变量的作用域

作用域是编译期概念, 描述对于一个名字(identifier), 从声明点到某个语法边界结束的区域, 使得该区域内可以通过”name lookup”找到该名字的声明. 非常粗略地来说分为3种:

  1. 局部作用域(代码块): 例如函数体, for循环的临时循环变量, if-else块等
  2. 类作用域: 可见性由访问控制影响
  3. 命名空间作用域/全局作用域: 定义在最外层的变量在整个文件可见

对于多文件(多翻译单元)的情况, 变量在翻译单元之间是否可见不属于作用域概念, 而是链接性.

  1. 外部链接: 可被其它翻译单元引用 例如非static非const的全局变量, 在另外的翻译单元有extern时可被另外的翻译单元发现;

  2. 内部链接: 仅本翻译单元可见 例如static全局变量, const全局变量, 匿名命名空间内的变量;

  3. 无链接: 局部作用域的变量没有链接性.;

变量的存储期

C++ 标准把变量(准确说对象)的存储持续时间主要分为:

  1. 自动存储期(automatic)

    块作用域局部变量(非 static、非 thread_local)通常是自动存储期。 通常分配在栈上(实现常见但不是标准强制)。

  2. 静态存储期(static storage duration)

    命名空间作用域(含全局)变量、static 局部变量、static 成员变量等。 程序开始到程序结束一直存在(但对象的“动态初始化/析构”有明确时机规则)。

  3. 线程存储期(thread storage duration)

    thread_local 变量。 每个线程各自拥有一份;从线程开始到线程结束。

  4. 动态存储期(dynamic storage duration)

    new 创建的对象(以及某些运行时分配器创建的对象)。 从分配成功并完成构造开始,到析构并释放结束(由你何时 delete 决定)。

不同变量类型的生命周期

  • 全局变量(包括静态全局变量)/命名空间作用域变量:

    • 静态存储期: 进入main函数前分配空间, 程序结束时析构销毁;
    • 静态存储区: 视其能否常量初始化, .data或.bss区.
    • 具有静态存储期的变量初始化分为零初始化(所有静态存储期的变量), 常量初始化(之前, 满足编译期常量表达式的变量), 动态初始化(调用构造函数). 对于全局变量(包括静态全局变量)/命名空间作用域变量, 在main()之前完成初始化.
  • 静态局部变量/静态成员变量

    • 静态存储期
    • 静态存储区
    • 与全局变量不同的是, 首次执行到该声明时进行动态初始化.
    • (线程安全性! 如果多线程执行有静态局部变量的函数, 何时申请空间? C++11之前线程不安全, C++11及以后线程安全)
  • 局部变量

    • 自动存储期
    • 栈区
  • 成员变量

    • 存储期和存储位置视其对象
  • const/constexptr

    • 若在代码块内, 自动存储期, 栈区
    • 若在命名空间/全局, 静态存取期, 可能在.rodata(只读区)或完全被优化掉存储空间(不需要其地址的情况下).

评论