c语言 - 为什么内存必须是连续的?如果不是这样,这不会解决内存碎片的问题吗?



我的背景知识:

据我所知,要正确分配/使用内存,内存在虚拟地址空间中必须是连续的,但在物理内存甚至物理内存地址空间中不必是连续的。

这在某种程度上表明,内存地址从物理工作转换为虚拟工作的方式是,它是一系列映射,其中物理内存地址空间中的任何空闲内存块都被分配到虚拟内存地址空间的相应区域。

问题设置:

在回答C中关于释放内存的问题时,这个答案指的是内存碎片,在这种情况下(在这种特定情况下)重复分配和释放内存可能会导致存在足够的操作系统分配的内存供未来进程使用,但它不能使用,因为它在自由存储链表中不连续。

如果我们可以继续从操作系统分配的内存中提取未使用的内存块,即使它们是分散的(不连续的),这难道不能解决内存碎片的问题吗?对我来说,这似乎与物理到虚拟内存地址转换的工作方式完全相同,其中非连续块被当作连续块来使用。

那么,为了重复我的问题,为什么内存必须是连续的

这里有两个问题:

  • 每个对象都必须占据虚拟内存中的一个连续区域,这样才能有效地进行索引和指针运算。如果你有一个数组int arr[5000];,那么像arr[i] = 0;这样的语句可以归结为简单的算术:i的值乘以4(或者sizeof(int)可能是什么),然后加到arr的基地址。对于CPU来说,添加速度非常快。如果arr的元素不位于连续的虚拟地址,那么arr[i]将需要一些更精细的计算,并且您的程序将慢几个数量级。同样,对于连续数组,像ptr++这样的指针运算实际上只是加法。

  • 虚拟内存具有粒度。虚拟地址到物理地址的每一次映射都需要在内存中的某个位置保存一些元数据(比如每个映射8个字节),当使用此映射时,它会由CPU缓存在转换后备缓冲区中,该缓冲区需要芯片上的一些硅。如果每个字节的内存都可以独立映射,那么映射表所需的内存将是程序本身的8倍,并且需要大量的TLB来缓存它们。

    因此,虚拟内存是以页面为单位完成的,通常为4KB或16KB左右。页面是虚拟内存的连续4K区域,映射到物理内存的连续4K区域。因此,整个页面只需要映射表(页面表)和TLB中的一个条目,并且页面内的地址是一一映射的(虚拟地址的低位直接用作物理地址的低位)。

但这意味着无法使用虚拟内存修复按子页面数量划分的碎片。在Steve Summit的例子中,假设您分配了1000个对象,每个对象1KB,这些对象连续存储在虚拟内存中。现在,您可以释放所有奇数对象。名义上,现在有500 KB的内存可用。但是,如果您现在想分配一个大小为2KB的新对象,那么这500 KB中没有一个是可用的,因为在您原来的1000 KB区域中没有大小为2KB的连续块。可用的1KB块无法重新映射以合并为一个更大的块,因为它们无法与共享页面的偶数对象分离。偶数对象不能在虚拟内存中移动,因为程序中的其他地方可能有指向这些对象的指针,而且没有很好的方法来更新它们。(进行垃圾收集的实现可能能够做到这一点,但大多数C/C++实现都不能做到,因为这本身就有相当大的成本。)

那么,重复我的问题,为什么内存必须是连续的?

它不必是连续的。

为了避免操作系统分配的内存页面块内出现碎片;您需要确保从";堆";(例如使用"malloc()")至少与OS分配的存储器的块的块一样大。这提供了两种可能的选择:

a) 改变硬件(和OS/软件),使得OS分配的存储器块要小得多(例如,可能与高速缓存线大小相同,或者可能是64字节而不是4 KiB)。这将显著增加管理虚拟内存的开销。

b) 更改堆的最小分配大小,使其更大。通常(对于现代系统);CCD_ 10";它将大小四舍五入到8个字节或16个字节以便对齐,并称之为"1";填充";。以同样的方式,它可以将大小四舍五入到操作系统分配的内存块的大小,并调用该"块";填充";(例如"malloc(1);"可能具有4095字节的填充并且花费4KIB的存储器)。这比碎片更糟糕,因为无法分配填充(例如,如果您执行了"malloc(1); malloc(1);",则这些分配不能使用操作系统分配的内存的同一块的不同部分)。

然而;这只适用于小额拨款。如果使用";CCD_ 13";为了分配大量内存(例如,对于一个阵列可能是1234KiB),大多数现代内存管理器只会使用操作系统分配的内存块,而没有理由关心这些大块的碎片。

换句话说;对于较小的分配,您可以按照建议的方式解决碎片问题,但这比允许一些碎片更糟糕;对于更大的分配,您可以按照建议的方式解决碎片问题,大多数现代内存管理器已经这样做了。

最新更新