程序内存布局

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++代码为例,看一下常见变量所属的内存段。

#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]深入理解计算机系统.虚拟内存

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IT派

Python 的异步 IO:Asyncio 简介

所谓「异步 IO」,就是你发起一个 IO 操作,却不用等它结束,你可以继续做其他事情,当它结束时,你会得到通知。

15330
来自专栏小程序·云开发专栏

云函数之间互相调用实现邮件发送

很多时候,我们会面临在小程序的后台实现多重功能,比如用户管理、日志分析、数据排序等,不同的功能之间还会相互调用。如果把这些功能都写到一个云函数里,会造成云函数逻...

7.8K20
来自专栏Python

数据库连接池,本地线程,上下文管理

一、数据库连接池 flask中是没有ORM的,如果在flask里要连接数据库有两种方式 一:pymysql 二:SQLAlchemy 是pyth...

49660
来自专栏happyJared

Linux私房菜:文件目录权限那点事

共有十个位,注意到第一个字符为[-],表示文件;常见的还有d,代表目录(directory);l,表示连接档(link)

10020
来自专栏逆向技术

框架原理第一讲,熟悉常用的设计方式.(以MFC框架讲解)

          框架原理第一讲,熟悉常用的设计方式.(以MFC框架讲解) 一丶什么是框架,以及框架的作用 什么是框架?   框架,简而言之就是把东西封装好了...

283110
来自专栏Golang语言社区

转--使用Revel(go)开发网站

Revel很好的利用了Go语言的goroutine,把每一个request都分配到了goroutine里。不用再写一大堆的回调。如果你写过nodejs的话就会深...

393100
来自专栏达摩兵的技术空间

你不知道的opn模块

opn模块通常是作为跨平台的打开文件或者网站的模块,在web应用中最常见的使用是比如项目开发或者启动的时候打开浏览器进行访问。

7810
来自专栏LanceToBigData

JavaWeb(一)Servlet中乱码解决与转发和重定向的区别

前言   前面其实已经把Servlet中所有的内容都介绍完了,这篇讲补充一点乱码和重定向与转发之间的区别! 一、request请求参数出现乱码问题 1.1、ge...

347100
来自专栏北京马哥教育

大神带你 20 分钟学会 Ansible !

64920
来自专栏大内老A

WCF服务端运行时架构体系详解[下篇]

作为WCF中一个核心概念,终结点在不同的语境中实际上指代不同的对象。站在服务描述的角度,我们所说的终结点实际上是指ServiceEndpoint对象。如果站在W...

22270

扫码关注云+社区

领取腾讯云代金券