C++内存分配方式与Linux地址空间

1.虚拟地址空间简介
虚拟地址空间(Virtual Address Space)是每一个程序被加载运行起来后,操作系统为进程分配的虚拟内存,它为每个进程提供了一个假象,即每个进程都在独占地使用主存。

每个进程所能访问的最大的虚拟地址空间由计算机的硬件平台决定,具体地说是由 CPU 的位数决定的。比如 32 位的 CPU 决定了虚拟地址空间的大小为 0 -2^32-1,即 0x00000000 - 0xFFFFFFFF,也就是我们常说的 4 GB 虚拟内存空间。如果是 64 位的CPU,那么寻址范围是 0 - 2^64 -1,即 0x0000000000000000 - 0xFFFFFFFFFFFFFFFF,共有 17 179 864 184 GB。

假设我们使用的是 32 位的硬件平台,4GB 的虚拟内存空间可以被用户程序完全占用吗?很显然,不行。因为除了用户进程,操作系统会独占一部分虚拟内存空间,用户进程只能使用操作系统分配给进程的地址空间,如果用户进程访问未经允许的地址空间,则会被操作系统判为非法请求,结果就是程序被操作系统强制结束。比如 Windows 下的“进程因非法操作需要关闭” 和 Linux 下的 “Segmentation fault”,一般都是由于进程访问了非法的内存地址。

对于 Linux,4GB 的虚拟地址空间的默认分配状态如下:


2.虚拟地址空间布局
C/C++程序为编译链接后生成可执行的二进制文件,由多个段组成,一般包含代码段、数据段和 BSS 段等。由于可执行文件段的数量较多,映射到虚拟地址空间时,由于段的大小往往并不是系统页大小的整数倍,多余部分也会占用一个页,这就会造成内存空间的浪费。当操作系统装载程序时,会进行优化,将多个相同属性的段合并成一个段进行装入,比如将相同权限的段合并成一个段进行映射。段的权限一般分为如下三种:
(1)以代码段 .text 为代表的可读可执行的段;
(2)以数据段 .data 和 未初始化数据段 .bss 为代表的可读可写的段;
(3)以只读数据段 .rodata 为代表的只读的段。

比如 .text 和 .init 段,分别包含程序的可执行代码和初始化代码,操作系统在装载程序时可以将这两个段合并成一个段(Segment)进行映射,以节省内存空间。

说到合并后的段(Segment)和合并之前的段(Section),虽然中文叫法相同,但对于英文称谓是不同的。合并后的段是 Segment,是程序装载时的概念,合并之前的段是 Section,是程序链接时的概念,需要加以区分。系统按照 Segment 进行装载映射可执行文件而不是 Section。

可执行文件载入内存运行时,在 Linux 环境下的虚拟地址空间由一般有代码段、初始化数据段、未初始化数据段、堆和栈构成,如果程序使用了内存映射文件(比如共享库、共享文件),那么包含映射段。Linux 环境程序典型的内存布局如下图所示。

有时候,把 BSS 段与 Data 段看做成一个可读写的数据段也是可以的,这里做了区分。下面简要说明程序装载时相关的段。

代码段(Text Segment),用户存放 CPU 执行的机器指令(机器指令(Machine Instructions)是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级)用户代码。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈,这与处于内核态进程的状态有些类似。

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


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

下面以 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] 深入理解计算机系统中文版[M].C1.7.3虚拟内存.P12-P14
[5] 深入理解计算机系统中文版[M].C9.7.2.Linux虚拟内存系统.P580-P581
[6] 俞甲子,石凡,等.程序员的自我修养——链接、装载与库[M].北京:电子工业出版社,2009-04.C6.4 进程虚存空间分布.P161-173
 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值