存储区, 作用域与生命周期(存储期)¶
约 1177 个字 预计阅读时间 4 分钟
内存的存储区¶
不同 OS/ABI/编译器会有差异,但典型用户态进程的虚拟地址空间可抽象为:
-
代码段(text / .text) 存放机器指令(函数体)。 通常只读+可执行。
-
只读数据段(rodata / .rodata) 字符串字面量、const 只读常量等可能放这里(实现决定)。 通常只读不可写。
-
已初始化的全局/静态数据段(data / .data) 有显式非零初始化的全局变量、静态变量、静态成员等。
-
未初始化或零初始化的全局/静态数据段(bss / .bss) 未显式初始化或初始化为 0 的全局/静态对象(语言层面会做零初始化)。
-
堆(heap / free store) new/delete、malloc/free 等动态分配区域(一般是“自由存储区”,常映射到堆实现)。 分配/释放由运行时/分配器管理。
-
栈(stack)(每进程程一份) 调用栈帧:返回地址、保存寄存器、函数参数、自动存储期局部变量等(具体布局实现相关)。 进入作用域“压栈”,离开作用域“出栈”。
-
内存映射区(mmap / 映射段)(OS 概念) 动态库、文件映射、匿名映射、大块分配等常在此区域。 对于linux系统, 线程栈也会在父进程的内存映射区。
变量的作用域¶
作用域是编译期概念, 描述对于一个名字(identifier), 从声明点到某个语法边界结束的区域, 使得该区域内可以通过”name lookup”找到该名字的声明. 非常粗略地来说分为3种:
- 局部作用域(代码块): 例如函数体, for循环的临时循环变量, if-else块等
- 类作用域: 可见性由访问控制影响
- 命名空间作用域/全局作用域: 定义在最外层的变量在整个文件可见
对于多文件(多翻译单元)的情况, 变量在翻译单元之间是否可见不属于作用域概念, 而是链接性.
-
外部链接: 可被其它翻译单元引用 例如非static非const的全局变量, 在另外的翻译单元有extern时可被另外的翻译单元发现;
-
内部链接: 仅本翻译单元可见 例如static全局变量, const全局变量, 匿名命名空间内的变量;
-
无链接: 局部作用域的变量没有链接性.;
变量的存储期¶
C++ 标准把变量(准确说对象)的存储持续时间主要分为:
-
自动存储期(automatic)
块作用域局部变量(非 static、非 thread_local)通常是自动存储期。 通常分配在栈上(实现常见但不是标准强制)。
-
静态存储期(static storage duration)
命名空间作用域(含全局)变量、static 局部变量、static 成员变量等。 程序开始到程序结束一直存在(但对象的“动态初始化/析构”有明确时机规则)。
-
线程存储期(thread storage duration)
thread_local 变量。 每个线程各自拥有一份;从线程开始到线程结束。
-
动态存储期(dynamic storage duration)
new 创建的对象(以及某些运行时分配器创建的对象)。 从分配成功并完成构造开始,到析构并释放结束(由你何时 delete 决定)。
不同变量类型的生命周期¶
-
全局变量(包括静态全局变量)/命名空间作用域变量:
- 静态存储期: 进入main函数前分配空间, 程序结束时析构销毁;
- 静态存储区: 视其能否常量初始化, .data或.bss区.
- 具有静态存储期的变量初始化分为零初始化(所有静态存储期的变量), 常量初始化(之前, 满足编译期常量表达式的变量), 动态初始化(调用构造函数). 对于全局变量(包括静态全局变量)/命名空间作用域变量, 在main()之前完成初始化.
-
静态局部变量/静态成员变量
- 静态存储期
- 静态存储区
- 与全局变量不同的是, 首次执行到该声明时进行动态初始化.
- (线程安全性! 如果多线程执行有静态局部变量的函数, 何时申请空间? C++11之前线程不安全, C++11及以后线程安全)
-
局部变量
- 自动存储期
- 栈区
-
成员变量
- 存储期和存储位置视其对象
-
const/constexptr
- 若在代码块内, 自动存储期, 栈区
- 若在命名空间/全局, 静态存取期, 可能在.rodata(只读区)或完全被优化掉存储空间(不需要其地址的情况下).