文章目录
前言
程序只是一段可以执行的代码文件,通俗讲在 linux 上就是一个可执行文件。当一个程序运行时就被称为进程,即进程是运行状态的程序。对32位处理器,虚拟内存空间为4G,每个进程都认为自己拥有4G的空间。虚拟内存又被划分为不同分区,用于存储不同类型的数据。
一、虚拟内存分布
1. 虚拟内存
对32位处理器,虚拟内存空间为4G,每个进程都认为自己拥有4G的空间,实际上,在虚拟内存对应的物理内存上,可能只对应的一点点的物理内存。进程得到的这4G虚拟内存是一个连续的地址空间(这也只是进程认为),而实际上,它通常是被分隔成多个物理内存碎片,还有一部分存储在外部磁盘存储器上,在需要时进行数据交换。
由于存在两个内存地址,因此一个应用程序从编写到被执行,需要进行两次映射。第一次是映射到虚拟内存空间,第二次时映射到物理内存空间。在计算机系统中,第两次映射的工作是由硬件和软件共同来完成的。承担这个任务的硬件部分叫做存储管理单元 MMU,软件部分就是操作系统的内存管理模块了。
参考资料:Linux C内存分配、程序内存分布
2. 进程的虚拟内存分布
进程的内存空间按照 代码段(TEXT)、初始化数据段(DATA)、未初始化数据段(BSS)、堆(HEAP)、文件映射段(MMS)、栈(STACK)、内核科技(Kernel)由低到高分配,但是只增值到0xC0000000, 最后1G留给了内核。0 ~ 3G属于用户空间,3 ~ 4G为内核空间。
- 程序文件段:包括程序的二进制可执行代码,只读。
- 已初始化数据段:包括已初始化的全局变量和静态常量。
- 未初始化数据段:包括未初始化的静态变量和全局变量。
- 堆段:包括动态分配的内存,从低地址开始向上增长。
- 文件映射段:包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关)。
- 栈段:包括函数的参数值和局部变量、函数调用的上下文等。栈的大小是固定的,一般是8MB。当然系统也提供了参数,以便我们自定义大小;栈区是从高地址位向低地址位增长的。
- 内核空间:包含内核栈和内核的数据段,所以内存地址生长方向既有由低到高(内核数据段),也有由高到低(内核栈)。关于读写的特点,由内核进行读写,用户程序不可直接访问。
3. 程序的内存分布
可执行程序在存储时(没有调入到内存)只有代码段(TEXT)、数据段(DATA)、未初始化数据段(BSS)3个部分。下图对比了可执行代码存储结构和运行时(进程)的内存结构对照:
4. 内存分配方式
- 静态和动态分配
- 静态分配:编译器在处理程序的源代码的时候由编译器分配。
- 动态分配:程序在执行过程中由程序员自己分配,堆内存调用malloc库函数申请分配,栈内存使用alloca申请分配(仅在Linux下)。
- 静态和动态分配的区别
- 静态对象的分配与释放由编译器自动处理;动态对象的分配和释放必须由程序员自己显示的管理,通过malloc 和free 两个函数来完成。
- 静态对象是有名字的变量,可以直接对其进行操作;动态对象是没有名字的变量,需要通过指针间接的对它进行操作。
段名 | 存储内容 | 分配方式 | 生长方向 | 读写特点 | 运行态 |
---|---|---|---|---|---|
代码段 | 程序指令、字符串常量、虚函数表 | 静态分配 | 由低到高 | 只读 | 用户态 |
数据段 | 初始化的全局变量和静态变量 | 静态分配 | 由低到高 | 可读可写 | 用户态 |
BSS段 | 未初始化的全局变量和静态变量 | 静态分配 | 由低到高 | 可读可写 | 用户态 |
堆 | 动态申请的数据 | 动态分配(Linux malloc) | 由低到高 | 可读可写 | 用户态 |
映射段 | 动态链接库、共享文件、匿名映射对象 | 动态分配 | 由低到高 | 可读可写 | 用户态 |
栈 | 局部变量、函数参数与返回值、函数返回地址、调用者环境信息 | 静态+动态(Linux alloca) | 由高到低 | 可读可写 | 用户态 |
内核空间 | 存储操作系统、驱动程序 | 静态+动态 | 由低到高 + 由高到低 | 不能直接访问 | 内核态 |
二、堆 & 栈
1. 不同变量所在的内存分配
- 静态存储区(数据段、BSS段):内存分配在程序编译之前完成,且在程序的整个运行期间都存在,例如全局变量、静态变量。
- 栈:在函数执行时,函数内的局部变量的存储单元在栈上创建,函数执行结束时这些存储单元自动释放局部变量、函数内参数都在栈上。
- 堆:
malloc
或new
开辟的空间在堆上。
2. 堆与栈有什么区别
- 申请方式
栈的空间由系统(编译器)自动分配/释放,堆上的空间手动分配/释放。- 申请大小的限制
栈空间有限:栈是向低地址扩展的数据结构,是一块连续的内存,栈的最大容量是系统预先规定好的。
堆空间较大:堆是向高地址扩展的数据结构,是不连续的内存区域,堆的大小受限于计算机系统中有效的虚拟内存。- 申请效率
栈由系统自动分配,速度较快但程序员是无法控制的。
堆是由new
/malloc
分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。
3. 待补充【栈在 C 语言中有什么作用、C 语言函数参数的压栈顺序、new/delete 与 malloc/free 的区别】
三、内存泄漏
1. 什么是内存泄漏
简单地说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,导致整个系统崩溃。
2. 如何判断内存泄漏
- 良好的编码习惯,尽量在涉及内存的程序段,检测出内存泄露当程式稳定之后,在来检测内存泄露时,无疑增加了排除的困难和复杂度使用了内存分配的函数,一旦使用完毕,要记得要使用其相应的函数释放掉 。
- 将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查该链表 。
- Boost 中的smart pointer。
- 一些常见的工具插件,如ccmalloc、Dmalloc、Leaky等等 。