C 内存空间
1. 基本概念
内存是计算机系统中一个主要部件, 用于保存进程运行时的程序和数据,也称可执行存储器。在计算机中,内存空间一般是指主存储器空间(物理地址空间)或系统为一个用户程序分配内存空间。
而 Linux 操作系统会对实际存在的物理内存进行映射,对应用程序屏蔽了物理内存的具体细节,有利于简化程序的编写和系统统一的管理。
- PM(Physical Memory):物理内存,实实在在的虚拟设备。
- VM(Virtual Memory):虚拟内存,操作系统映射虚拟出来的内存。
2. 进程内存布局
- 每个C语言进程都拥有一片结构相同的虚拟内存
例如三个完全不相干的进程p1、p2、p3,它们很显然会占据不同区段的物理内存,但经过系统的变换和映射,它们的虚拟内存的布局是完全一样的。
- 将其中一个C语言含进程的虚拟内存放大看,会发现其内部包含了:栈、堆、数据段、代码段。
虚拟内存中,虚拟内存中,内核区段对于应用程序而言是禁闭的,它们用于存放操作系统的关键性代码,另外由于 Linux 系统的历史性原因,在虚拟内存的最底端0x0 ~ 0x08048000 之间也有一段禁闭的区段,该区段也是不可访问的。
- 虚拟内存中各个区段的详细内容。
3. 栈内存
栈内存指的是从 0xC000 0000 往下增长的这部分内存区域,进程在使用栈内存时是严格按照 “先进后出” 的原则来操作的。
栈的全称是“运行时栈(run-timestack)”,顾名思义栈会随着进程的运行而不断发生变化:一旦有新的函数被调用,就会立即在栈顶分配一帧内存,专门用于存放该函数内定义的局部变量(包括所有的形参),当一个函数执行完毕返回之后,他所占用的那帧内存将被立即释放,在上图中用一根虚线和箭头来表示栈的这种动态特征。
-
什么东西存储在栈内存中?
- 环境变量
- 命令行参数
- 局部变量(包括形参)
-
栈内存有什么特点?
- 空间有限,尤其在嵌入式环境下。因此不可以用来存储尺寸太大的变量。
- 每当一个函数被调用,栈就会向下增长一段,用以存储该函数的局部变量。
- 每当一个函数退出,栈就会向上缩减一段,将该函数的局部变量所占内存归还给系统。
注意:栈内存的分配和释放,都是由系统规定的, 我们无法干预。
4. 堆内存
4.1 堆内存基本概念
堆内寸是一块自由内存,又被称为动态内存,原因是在这个区域定义和释放变量完全由你来决定,即所谓的自由区。堆跟栈的最大区别在于堆是不设大小限制的,最大值取决于系统的物理内存。
堆的全称是“运行时堆(run-timeheap)”,跟栈一样,会随着进程的运行而不断地增大或缩小。它是一个自由区,此区域定义的内存的生命周期我们是可以控制的,开发者可以根据需要申请内存的大小,决定使用的时间长短等。
堆内存的生命周期是:从malloc()/calloc()/realloc()开始,到free()结束,其分配和释放完全由我们开发者自定义,这就给了我们最大的自由和灵活性,让程序在运行的过程当中,以最大的效益使用内存。
4.2 内存操作函数
C 语言为内存的分配和管理提供了几个函数。这些函数可以在头文件 #include < stdlib.h > 中找到。
- malloc( )
- calloc( )
- realloc( )
- free( )
- memset( )
注意:
- calloc 函数在申请完内存后,自动初始化该内存空间为零。
- malloc 函数不进行初始化操作,里边数据是随机的。
- memset 函数按照字符数组的方式操作内存对象,其主要目的是提供一个高效的函数接口,通常用于初始化 malloc 函数申请的内存对象。
realloc 注意:
- 如果用realloc新分配的内存空间比原来的大,则旧内存块的数据不会发生改变;如果新的内存空间大小小于旧的内存空间,可能会导致数据丢失,慎用!
- 该函数将移动内存空间的数据并返回新的指针。
- 如果 ptr 参数为 NULL,那么调用该函数就相当于调用 malloc(size)。
- 如果 size 参数为 0,并且 ptr 参数不为 NULL,那么调用该函数就相当于调用 free(ptr)。
- 除非 ptr 参数为 NULL,否则 ptr 的值必须由先前调用 malloc、calloc 或 realloc 函数返回。
5. 数据段
数据段实际上分为三部分,地址从高到底分别是.bss段、.data段和.rodata段三个数据段各司其职:
- .bss专门用来存放未初始化的静态数据,它们都将被初始化为0。
- .data段专门存放已经初始化的静态数据,这么初始值从程序文件中拷贝而来。
- .rodata段用来存放只读数据,即常量,比如进程中所有的字符串、字符常量、整型浮点型常量等。
6. 代码段
代码段实际上也至少分为两部分:.text段和.init段。
- .text段用来存放用户程序代码,也就是包括main函数在内的所有用户自定义函数。
- .init段则用来存储系统给每一个可执行程序自动添加的“初始化”代码,这部分代码功能包括环境变量的准备、命令行参数的组织和传递等,并且这部分数据被放置在了栈底。