让我们将这个C ++代码视为一个粗略的例子。
int *A = new int [5];
int *B = new int [5];
int *C = new int [5];
delete []A;
delete []C;
int *D = new int [10];
显然,任何机器都可以处理这种情况,而不会出现缓冲区溢出或内存泄漏问题。但是,让我们想象长度乘以一百万甚至更大的数字。据我所知,所有数组元素的地址(至少是虚拟地址)都是连续的。因此,每当我创建一个数组时,我可以确定它们是虚拟内存中的连续块,如果我有指向第一个元素的指针,我可以执行指针运算来访问第n个元素。我的问题在下图中说明(为简单起见,忽略表示数组末尾的寄存器)。
在堆中分配A,B,C之后,我们释放A和C并获得两个长度为5的空闲内存块(用绿点标记)。当我想分配一个长度为10的数组时会发生什么?我认为有3种可能的情况。
数组D将被分成两部分并且不连续存储,导致数组的第n个元素没有恒定的访问时间(如果有超过2个分裂,它开始类似于链表而不是一个数组)。
其中哪一个是最可能的答案,还是有其他可能的情况我没有考虑到?
答案 0 :(得分:7)
你问的问题叫做堆碎片,这是一个真正的难题。
由于没有连续的10长度内存块,我将得到bad_alloc异常。
这是理论。但这种情况实际上只能在32位进程中实现; 64位地址空间很大。
也就是说,使用64位进程,堆碎片更有可能阻止您的new
实现重用某些内存,这会导致内存不足,因为它需要询问内核对于整个D
数组而不是其中一半的新内存。此外,当您尝试访问D
中的某个位置而不是new
抛出异常时,这样的OOM条件更有可能导致您的进程被OOM杀手攻击,因为内核我们不会意识到它已经过度使用了它的记忆。有关更多信息,请谷歌“内存过量使用”。
程序会自动将数组B重新分配到堆的开头,并将剩余的未使用内存连接在一起。
不,它不能。您使用的是C ++,并且您的运行时不知道您可能存储指向B
的指针的位置,因此它会冒失去需要修改的指针的危险,或者冒着修改某些不需要的指针的危险指向B
的指针,但恰好具有相同的位模式。
数组D将被分成两部分并且不连续存储导致数组的第n个元素不能持续访问时间(如果有超过2个分裂,它开始类似于链表而不是数组)
这也是不可能的,因为C ++保证了数组的连续存储(允许通过指针算法实现数组访问)。
答案 1 :(得分:7)
由于没有连续的10长度内存块,我将得到bad_alloc异常。
这可能发生。
程序会自动将数组B重新分配到堆的开头,并将剩余的未使用内存连接在一起。
这不可能发生。在C ++中无法将对象移动到其他地址,因为它会使现有指针无效。
数组D将被分成两部分并且不连续存储导致数组的第n个元素不能持续访问时间(如果有超过2个分裂,它开始类似于链表而不是数组)
这也不可能发生。在C ++中,数组元素是连续存储的,因此可以进行指针运算。
但实际上有更多的可能性。要理解它们,我们必须考虑到内存可以是虚拟的这一事实。除其他外,这意味着可用地址空间可能大于物理存在的存储器的数量。可以为可用地址空间中的任何地址分配一块物理内存。
例如,考虑一台在64位CPU上运行64位操作系统的8GB(2 ^ 33字节)内存的计算机。分配给该程序的地址不会低于8GB;它可以在地址0x00000000ffff0000接收一兆字节的内存,在地址0x0000ffffffff0000接收另一兆字节的内存。分配给程序的内存总量不能超过2 ^ 33个字节,但每个块可以位于2 ^ 64空间中的任何位置。 (实际上这有点复杂,但与我描述的相似)。
在你的照片中,你有15个代表大块记忆的小方块。让我们说它是物理记忆。虚拟内存是15,000个小方块,在任何给定时间你都可以使用任何 15。
因此,考虑到这一事实,以下情况也是可能的。