1

由内存分配引发的回忆

 1 year ago
source link: https://muyuuuu.github.io/2022/07/11/memory-allocation/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

由内存分配引发的回忆

2022-07-11

1 1.9k 2 分钟

闲来无事,在面经上看到了一个问题:在物理机只有 1G 内存的情况下,能否 malloc 出 4G 大小的数组。奇怪的是,这个问题在网上搜不到特别好的解答,于是突发奇想试着解答一下。

先直接给出结论,malloc 的内存位于堆区,顺便简单了解下 C/C++ 的内存分布。对于 C/C++ 语言,程序内存分布如下:

  • 栈区(stack):由编译器自动分配释放,存储函数的参数值,局部变量值等,其操作方法类似于数据结构中的栈
  • 堆区(heap):一般由程序员申请和释放,与数据结构中的堆没有任何关系,分配方式类似于链表
  • 全局/静态区(static):全局变量和静态变量是存储在一起的,在程序编译时分配
  • 文字常量区:存储常量字符串
  • 程序代码区:存储函数体(类的成员函数、全局函数)的二进制代码

重点是其中的栈区和堆区:

  • 栈区:程序自动向操作系统申请分配以及回收,速度快,使用方便,但是程序员无法控制。如果分配的内存超过了栈区的最大空间,会抛出栈溢出错误。const 局部变量也存储在栈区,栈区向地址减小的方向增长。系统为变量在栈上申请内存后,CPU 需要不断地判断变量是否已结束使用的生命周期,如果生命周期结束,系统就会释放为这个变量申请的栈内存,这样一来随着在栈上申请的变量增多,会对 CPU 造成额外的消耗。

  • 堆区:程序员向操作系统申请一段内存,当系统收到程序的申请时,会遍历一个记录空内存结点的链表,找到第一个空间大于或等于所申请空间的堆结点,将该空闲结点从链表中删除,并将该结点的空间分配给程序,如果链表中空闲结点的空间大于申请空间的大小,系统会自动将对于的部分放入空闲链表中,故容易造成内存的碎片化,分配速度较慢,地址不连续。且堆区的内存由程序员申请,也必须由程序员负责管理和释放,否则会导致内存泄漏,堆的增长方向与内存地址的增长方向相同,因此在堆区上申请空间理论上是没有大小限制的,但是受安装内存条的大小和系统以及其他程序的占用,不是无限大的。不像栈上的变量那样,需要消耗 CPU 资源判断变量的生命周期,所以不会对 CPU 造成额外的消耗,这也是程序员申请堆上内存的优点。

对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生内存泄露。碎片问题:对于堆来讲,频繁的 malloc/free 势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出。

1G 内存 4G 数组

在了解 malloc 分配到的堆区大小取决于内存剩余的空闲空间后,再来研究能不能分配出大于空闲空间的数据。先给出结论,在虚拟内存足够大的情况下,1G 大小的内存可以开辟出 4G 的数组。虚拟内存是一个假象的内存空间,在程序运行过程中虚拟内存空间中需要被访问的部分会被映射到物理内存空间中。虚拟内空间大只能表示程序运行过程中可访问的空间比较大,不代表物理内存空间占用也大。

malloc 可以申请到超出机器物理内存的大小,为什么说是一部分呢,因为可申请的内存不仅和已占用的内存相关,还和机器的 swap space (虚拟内存)相关,事实上在你给你机器装 Linux 系统的时候应该碰到过,那就是磁盘分区的时候会有一个 swap设定,只需要知道它是一种挂载在物理硬盘上,用来存放一些不太频繁使用的内存,是一种低速的物理内存的扩展。

当物理内存不够用时,原先一些物理内存中不常访问的内容会被转移到这里以让出空间给其它进程。所以 swap 空间也可以被 malloc 申请到。malloc 这个时候申请了内存,但没有完全申请,这就涉及到一个叫做 Lazy Allocation 的东东,当你使用 malloc 时,系统并没有真正从物理内存中分配,而是等到进程要操作时才提供 allocation

因此,正是因为虚拟内存的存在,通过虚拟内存可以让程序可以拥有超过系统物理内存大小的可用内存空间。

虚拟内存扩展

这是我研究生第一节课老师讲述的内容(顿时我就觉得那老师才是真正的计算机学者):虚拟内存为每个进程提供了一个一致的、私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉。使用虚拟寻址,CPU 需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区,不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存,如果各个进程之间没有独立的地址空间,一个进程由于执行错误指令或是恶意代码都可以直接修改其它进程的数据,甚至修改内核地址空间的数据,这是操作系统所不愿看到的。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK