【问题标题】:C++ allocating array non contiguouslyC ++非连续分配数组
【发布时间】:2016-01-31 06:43:16
【问题描述】:

让我们将这段 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种可能的情况。

  • 我会因为没有连续的 10 长内存块而得到 bad_alloc 异常。

  • 程序会自动将数组 B 重新分配到堆的开头,并将其余未使用的内存连接在一起。

  • 数组 D 将被拆分为 2 部分并且不连续存储,导致数组的第 n 个元素的访问时间不恒定(如果拆分多于 2 个,则它开始类似于链表而不是数组) .

    其中哪一个是最可能的答案,还是我没有考虑其他可能的情况?

【问题讨论】:

  • 这非常依赖于操作系统。
  • @JamesAdkison 是的,假设我们只有 15 个堆内存寄存器。否则这个问题将毫无意义。
  • 它还忽略了程序使用虚拟内存而不是物理硬件内存这一事实。这更像是一个理论问题。理论上的答案是期望newmalloc 在后续请求大于可用内存时返回NULL
  • 您的标题非常笼统。请让它识别问题。
  • @n.m.所以你说的内存分配和这样的转移在 Arduino 上和在 Windows 上是一样的吗?我觉得像 RTEMS 这样的 RTOS 在遇到碎片内存时肯定会与 OSX 表现不同(即 RTEMS 什么都不做 OSX 可能会做一些事情)...

标签: c++ arrays memory memory-management


【解决方案1】:

我会因为没有连续的 10 长内存块而得到 bad_alloc 异常。

这可能发生。

程序会自动将数组 B 重新分配到堆的开头,并将其余未使用的内存连接在一起。

这不可能发生。在 C++ 中无法将对象移动到不同的地址,因为这会使现有指针无效。

数组 D 将被拆分为 2 部分并且不连续存储,导致数组的第 n 个元素的访问时间不恒定(如果拆分多于 2 个,则它开始类似于链表而不是数组) .

这也不可能发生。在 C++ 中,数组元素是连续存储的,因此可以进行指针运算。

但实际上还有更多的可能性。要理解它们,我们必须考虑到内存可以是虚拟的这一事实。这意味着可用地址空间可能大于实际存在的内存量。可以为一块物理内存分配可用地址空间中的任何地址。

例如,假设一台具有 8GB(2^33 字节)内存的机器在 64 位 CPU 上运行 64 位操作系统。分配给程序的地址不都小于 8GB;它可以在地址 0x00000000ffff0000 处接收一个兆字节的内存块,在地址 0x0000ffffffff0000 处接收另一个兆字节的块。分配给程序的内存总量不能超过 2^33 字节,但每个块可以位于 2^64 空间中的任何位置。 (实际上这有点复杂,但与我描述的差不多)。

在你的图片中,你有 15 个小方块代表记忆块。假设它是物理内存。虚拟内存是 15,000 个小方块,您可以在任何给定时间使用其中的 15 个任意

因此,考虑到这一事实,以下情况也是可能的。

  • 将一块虚拟地址空间分配给不受实际物理内存支持的程序。当程序试图访问这个空间时,操作系统将尝试分配物理内存并将其映射到相应的地址,以便程序可以继续运行。如果此尝试失败,则该程序可能会被操作系统杀死。新释放的内存现在可供其他可能需要它的程序使用。
  • 两个短的内存块被映射到新的虚拟地址,以便它们在虚拟内存空间中形成一个长的连续块。请记住,通常虚拟内存地址比物理内存多得多,并且通常很容易找到未分配的范围。通常,这种情况只有在所讨论的内存块很大时才会实现。

【讨论】:

    【解决方案2】:

    您要问的问题称为堆碎片,这是一个真正的难题。

    我会因为没有连续的 10 长内存块而得到 bad_alloc 异常。

    这就是理论。但是这种情况实际上只有在 32 位进程中才有可能; 64 位地址空间是巨大的

    也就是说,对于 64 位进程,堆碎片更有可能阻止您的 new 实现重用一些内存,这会导致内存不足的情况,因为它需要向内核询问新的整个D 数组的内存而不是其中的一半。此外,当您尝试访问D 中的位置时,这种OOM 条件更有可能导致您的进程被OOM-killer 射杀,而不是new 抛出异常,因为内核不会意识到它在为时已晚之前已经过度使用了它的内存。更多信息,谷歌“内存过度使用”。

    程序会自动将数组 B 重新分配到堆的开头,并将其余未使用的内存连接在一起。

    不,它不能。您在 C++ 中,并且您的运行时不知道您可能将指向 B 的指针存储在哪里,因此它会冒着丢失需要修改的指针的危险,或者冒着修改不是指针的东西的危险到B,但恰好有相同的位模式。

    数组 D 将被拆分为 2 部分并且不连续存储,导致数组的第 n 个元素的访问时间不恒定(如果拆分多于 2 个,则它开始类似于链表而不是数组) .

    这也是不可能的,因为 C++ 保证数组的连续存储(允许通过指针算法实现数组访问)。

    【讨论】:

    • 内存过度使用仅发生在某些操作系统上,并且仅当您没有通过ulimit 或类似工具合理地限制程序可用的内存时。
    • @n.m.没错,但至少它会影响所有 linux 安装,这意味着绝大多数计算设备都会过度使用内存。不过,我不知道窗户。他们不也做内存过度使用吗?
    • 嵌入式设备通常会关闭过度使用(或者至少应该关闭!)
    猜你喜欢
    • 2014-02-08
    • 2012-08-14
    • 2016-12-30
    • 1970-01-01
    • 1970-01-01
    • 2017-02-09
    • 1970-01-01
    • 2015-01-15
    • 1970-01-01
    相关资源
    最近更新 更多