为什么连续调用new[]不会分配连续的内存?

3
我使用的是Ubuntu 14.04 64位系统。以下是我用于查看内存使用情况的C++代码。
int main() {
  int **ptr;

  ptr = new int* [2];
  cout << &ptr << " -> " << ptr << endl; 

  for (int r = 1; r <= 2; r++) {
    ptr[r-1] = new int [2 * r];
    cout << &ptr[r-1] << " -> " << ptr[r-1] << endl;

    for (int c = 0; c < 2 * r; c++) {       
        ptr[r-1][c] = r * c;
        cout << &ptr[r-1][c] << " -> " << ptr[r-1][c] << endl;
    }
  }

  return 0;
}

这是我的输出结果:
0x7fff09faf018 -> 0x1195010
0x1195010 -> 0x1195030
0x1195030 -> 0
0x1195034 -> 1
0x1195018 -> 0x1195050
0x1195050 -> 0
0x1195054 -> 2
0x1195058 -> 4
0x119505c -> 6

我本以为操作系统会连续分配内存。所以ptr[0][0]应该位于0x1195020而不是0x1195030!那么操作系统在0x1195020 - 0x119502F、0x1195038 - 0x119504F处使用了什么?


1
你不直接从操作系统中分配内存。运行时管理其自己的堆,而这个堆又是从操作系统中分配的。不能保证连续调用new()。在你的代码中永远不要假设它是连续的。 - OldProgrammer
如果你认为操作系统会连续分配内存,那么为什么你要使用ptr[r-1]而不是直接使用ptr +(偏移0x10字节)作为ptr[r-1]? - Jim Yang
此外,每次你使用malloc()分配内存或者new[]创建数组时,运行时系统都会额外添加一些字节来跟踪已分配的内存量/对象数量,这样当你稍后使用free()释放内存或者delete[]删除数组时,它就知道需要清理多少内存。 - Alex MDC
我建议你阅读关于Unix系统上动态内存分配的内容。例如这篇文章:https://www.ibm.com/developerworks/community/blogs/58e72888-6340-46ac-b488-d31aa4058e9c/entry/dynamic_memory_allocation_in_c4?lang=en 。引用自该文章:"当您使用malloc分配一个块时,它实际上会分配比您请求的内存多一些的内存。这些额外的内存用于存储诸如已分配块的大小、链中下一个空闲/已使用块的链接以及有时是一些“保护数据”之类的信息"。 - Diego C Nascimento
不能保证第一个块的分配地址比第二个块低,而且分配模式很可能取决于分配大小(较大的块与较小的块分别分配)。你只能安全地假定由一个 new 分配的空间不会与当前分配的任何其他块重叠。 - Jonathan Leffler
1个回答

9

因为:

  1. 每个分配的内存块的开头和结尾通常用于簿记。 (特别是,许多分配器发现在那里存储前/后块的大小或指向它们的指针很有用。)

  2. 内存分配器可能会将分配块的大小“舍入”以使其更容易处理。例如,如果分配了7字节,则很可能会将其舍入为8字节,即使是16或32。

  3. 内存块可能已经在非连续位置上可用。(请记住,在main()运行之前,C运行时可能已经进行了一些内存分配。)

  4. 分配器可能已经计划好了内存布局,放下一个块会破坏它的计划。(例如,它可能已经将该内存保留给特定大小的分配。)

  5. 为什么要这样做呢?没有任何保证。分配的内存可以随处放置。(嗯,几乎是这样。)不要做任何假设,只需让内存去到分配器说它会去的地方,你就没问题了。


2
此外,一些分配器根据大小将内存块进行分离。这通常是为了最小化碎片化而做的。 - Raymond Chen
谢谢。已经添加了类似的内容。 - user149341

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接