首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >程序内存布局

程序内存布局

作者头像
恋喵大鲤鱼
发布于 2018-08-03 17:35:53
发布于 2018-08-03 17:35:53
2.6K0
举报
文章被收录于专栏:C/C++基础C/C++基础

C/C++程序为编译后的二进制文件,运行时载入内存,运行时内存分布由代码段、初始化数据段、未初始化数据段、堆和栈构成,如果程序使用了内存映射文件(比如共享库、共享文件),那么包含映射段。Linux环境程序典型的内存布局如图1-5所示。

代码段(Text Segment)用户存放CPU执行的机器指令,未防止指令并其它程序修改,代码段一般只读不可更改。比如,源码中的字符串常量存储于代码段,不可修改。

初始化数据段(Data Segment)又称为数据段,用于存储初始化的全局变量和Static变量,段大小在编译时确定,所以内存的分配属于静态内存分配。

未初始化数据段(BSS Segment,Block Started by Symbol),又称为BSS段,通常用来存放程序中未初始化的全局变量和Static,虽未显示初始化,但在程序载入内存执行时,由内核清0,所以未显示初始化则默认为0。BSS段的大小也是在编译时确定,运行时内存的分配属于静态内存分配。

堆(Heap),用于保存程序运行时动态申请的内存空间,由开发人员手动申请,手动释放,若不手动释放,程序结束后由系统回收,生命周期是整个程序运行期间,比如使用malloc()或new申请的内存空间。堆的地址空间“向上增加”,即当堆上保存的数据越多,堆的地址就越高。堆的内存分配属于动态分配,一般运行时才知道分配的内存大小,并且堆可分配存活于函数之外的内存,在未显示调用free()或delete释放时,其生命周期为进程的生命周期。

映射段(Memory Mapping Segment),该区域内核将文件内容直接映射到内存。任何应用程序都可以请求该区域。Linux中通过mmap()系统调用,Windows中通过creatFileMapping()/MapViewOfFile()创建。文件I/O时内存映射方便并且高效,所以,它常用来加载动态库,还可以创建一种匿名映射,并不对应于文件,而用于程序数据。在Linux中,如果使用malloc()申请一块过大的内存,C库函数便会创建这种内存映射段,而不是使用堆内存。“过大”的内存指超过M_MMAP_THRESHOLD字节,默认128KB,可以通过mallopt()函数调整。映射段也属于动态分配。

栈(Stack),用于保存函数的局部变量(但不包括static声明的静态变量,静态变量存放在数据段或BSS段)、参数、返回值、函数返回地址以及调用者环境信息(比如寄存器值)等信息,由系统进行内存的管理,在函数完成执行后,系统自行释放栈区内存,不需要用户管理。整个程序的栈区的大小可以由用户自行设定,Windows默认的栈区大小为1M,可通过Visual Studio更改编译参数手动更改栈的大小。64bits的Linux默认栈大小为10MB,可通过命令ulimit -s临时修改。栈是一种“后进先出”(Last In First Out,LIFO)的数据结构,这意味着最后入栈的数据,将会是第一个出栈的数据。对于那些暂时存贮无需长期保存的信息来说,LIFO这种数据结构非常理想。在调用函数后,系统通常会清除栈上保存的信息。栈另外一个重要的特征是,它的地址空间“向下减少”,即当栈上保存的数据越多,栈的地址就越低。

内核空间(Kernel Space), 用于存储操作系统和驱动程序,用户空间用于存储用户的应用程序,二者不能简单地使用指针传递数据。当一个进程执行系统调用而陷入内核空间执行内核代码时,我们称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态),即此时处理器在执行最低特权级(3级)用户代码中。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。这与处于内核态的进程的状态有些类似。

内存段的特点和区别如下。

段名

存储内容

分配方式

生长方向

读写特点

运行态

代码段

程序指令、字符串常量、虚函数表

静态分配

由低到高

只读

用户态

数据段

初始化的全局变量和静态变量

静态分配

由低到高

可读可写

用户态

BSS段

未初始化的全局变量和静态变量

静态分配

由低到高

可读可写

用户态

堆

动态申请的数据

动态分配

由低到高

可读可写

用户态

映射段

动态链接库、共享文件、匿名映射对象

动态分配

由低到高

可读可写

用户态

栈

局部变量、函数参数与返回值、函数返回地址、调用者环境信息

静态分配

由高到低

可读可写

用户态

内核空间

储操作系统、驱动程序

动态+静态

由低到高+由高到低

不能直接访问

内核态

关于内核空间,其中包含内核栈和内核的数据段,所以内存地址生长方向既有由低到高(内核数据段),也有由高到低(内核栈)。关于读写的特点,由内核进行读写,用户程序不可直接访问。

以下面的C++代码为例,看一下常见变量所属的内存段。

代码语言:javascript
复制
#include <string.h>

int a = 0;                      // a在数据段,0为文字常量,在代码段
char *p1;                       // BSS段,系统默认初始化为NULL
void main()
{
    int b;                      //栈
    char *p2 = "123456";        //123456在代码段,p2在栈上
    static int c =0;            //c在数据段
    const int d=0;          //栈
    static const int d;     //数据段
    p1 = (char*)malloc(10); //分配得的10字节在堆
    strcpy(p1,"123456");        //123456放在字符串常量区,编译器可能会将它与p2所指向的"123456"优化成一个地方
}

以上所有代码,编译成二进制机器指令存放于代码段,不可修改。


参考文献

[1]linux内核空间和用户空间详解 [2]程序或-内存区域分配(五个段)–终于搞明白了 [3]进程内存分布剖析 [4]深入理解计算机系统.虚拟内存

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015年05月09日,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 参考文献
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档