跳到主要内容

C 内存空间

1. 基本概念

内存是计算机系统中一个主要部件, 用于保存进程运行时的程序和数据,也称可执行存储器。在计算机中,内存空间一般是指主存储器空间(物理地址空间)或系统为一个用户程序分配内存空间。

而 Linux 操作系统会对实际存在的物理内存进行映射,对应用程序屏蔽了物理内存的具体细节,有利于简化程序的编写和系统统一的管理。

  • PM(Physical Memory):物理内存,实实在在的虚拟设备。
  • VM(Virtual Memory):虚拟内存,操作系统映射虚拟出来的内存。

2. 进程内存布局

  • 每个C语言进程都拥有一片结构相同的虚拟内存

例如三个完全不相干的进程p1、p2、p3,它们很显然会占据不同区段的物理内存,但经过系统的变换和映射,它们的虚拟内存的布局是完全一样的。

ZfQEru.png

  • 将其中一个C语言含进程的虚拟内存放大看,会发现其内部包含了:栈、堆、数据段、代码段

ZfQxct.png

虚拟内存中,虚拟内存中,内核区段对于应用程序而言是禁闭的,它们用于存放操作系统的关键性代码,另外由于 Linux 系统的历史性原因,在虚拟内存的最底端0x0 ~ 0x08048000 之间也有一段禁闭的区段,该区段也是不可访问的。

  • 虚拟内存中各个区段的详细内容。

ZfQsME.png

3. 栈内存

栈内存指的是从 0xC000 0000 往下增长的这部分内存区域,进程在使用栈内存时是严格按照 “先进后出” 的原则来操作的。

栈的全称是“运行时栈(run-timestack)”,顾名思义栈会随着进程的运行而不断发生变化:一旦有新的函数被调用,就会立即在栈顶分配一帧内存,专门用于存放该函数内定义的局部变量(包括所有的形参),当一个函数执行完毕返回之后,他所占用的那帧内存将被立即释放,在上图中用一根虚线和箭头来表示栈的这种动态特征。

  • 什么东西存储在栈内存中?

    • 环境变量
    • 命令行参数
    • 局部变量(包括形参)
  • 栈内存有什么特点?

    • 空间有限,尤其在嵌入式环境下。因此不可以用来存储尺寸太大的变量。
    • 每当一个函数被调用,栈就会向下增长一段,用以存储该函数的局部变量。
    • 每当一个函数退出,栈就会向上缩减一段,将该函数的局部变量所占内存归还给系统。

注意:栈内存的分配和释放,都是由系统规定的,我们无法干预。

ZfQiyM.png

4. 堆内存

4.1 堆内存基本概念

堆内寸是一块自由内存,又被称为动态内存,原因是在这个区域定义和释放变量完全由你来决定,即所谓的自由区。堆跟栈的最大区别在于堆是不设大小限制的,最大值取决于系统的物理内存。

堆的全称是“运行时堆(run-timeheap)”,跟栈一样,会随着进程的运行而不断地增大或缩小。它是一个自由区,此区域定义的内存的生命周期我们是可以控制的,开发者可以根据需要申请内存的大小,决定使用的时间长短等。

堆内存的生命周期是:从malloc()/calloc()/realloc()开始,到free()结束,其分配和释放完全由我们开发者自定义,这就给了我们最大的自由和灵活性,让程序在运行的过程当中,以最大的效益使用内存。

ZfQHmP.png

4.2 内存操作函数

C 语言为内存的分配和管理提供了几个函数。这些函数可以在头文件 #include < stdlib.h > 中找到。

  • malloc( )

ZfzIND.png

  • calloc( )

Zfz6uS.png

  • realloc( )

Zfz7Rb.png

  • free( )

ZfzDLo.png

  • memset( )

Zf73Q0.png

注意:

  • 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段用来存放只读数据,即常量,比如进程中所有的字符串、字符常量、整型浮点型常量等。

ZfQzXT.png

6. 代码段

代码段实际上也至少分为两部分:.text段和.init段。

  • .text段用来存放用户程序代码,也就是包括main函数在内的所有用户自定义函数。
  • .init段则用来存储系统给每一个可执行程序自动添加的“初始化”代码,这部分代码功能包括环境变量的准备、命令行参数的组织和传递等,并且这部分数据被放置在了栈底。

ZfQ721.png