8

与静态数组相比,使用 malloc(除了失败时返回 NULL)有什么优势?以下程序将占用我所有的内存并仅在未注释循环时才开始填充交换。它不会崩溃。

...

#include <stdio.h>

unsigned int bigint[ 1u << 29 - 1 ];
unsigned char bigchar[ 1u << 31 - 1 ];

int main (int argc, char **argv) {
  int i;
/*   for (i = 0; i < 1u << 29 - 1; i++) bigint[i] = i; */
/*   for (i = 0; i < 1u << 31 - 1; i++) bigchar[i] = i & 0xFF; */

  getchar();
  return 0;
}

...

经过反复试验,我发现上面是我的 32 位 Intel 机器上允许使用 GCC 4.3 的最大静态数组。这是标准限制、编译器限制还是机器限制?显然,我想拥有多少就拥有多少。它会出现段错误,但前提是我要求(并尝试使用)多于 malloc 无论如何都会给我。

有没有办法确定静态数组是否实际分配并安全使用?

编辑:我对为什么使用 malloc 来管理堆而不是让虚拟内存系统处理它感兴趣。显然,我可以将数组的大小设置为我认为需要的大小的许多倍,并且虚拟内存系统只会将所需的内容保存在 ram 中。如果我从不写这些巨大数组的结尾(或开头),那么程序就不会使用物理内存。此外,如果我可以写入每个位置,那么除了在堆中增加一个指针或在同一进程中搜索先前的分配之外,malloc 还能做什么?

编者注: 如果 int 是 32 位,1 << 31 则会导致未定义的行为1u,因此我已将问题修改为 read 。该问题的目的是询问有关分配大型静态缓冲区的问题。

4

8 回答 8

16

好吧,实际上有两个原因:

  1. 由于可移植性,有些系统不会为您进行虚拟内存管理。

  2. 您将不可避免地需要将此数组划分为更小的块以使其有用,然后跟踪所有块,然后最终当您开始“释放”您不再需要的数组块时,您会遇到内存碎片的问题。

总而言之,您最终将实现大量内存管理功能(实际上几乎是重新实现 malloc)而没有可移植性的好处。

因此原因:

  • 通过内存管理封装和标准化实现代码可移植性。

  • 通过代码重用的方式提高个人生产力。

于 2009-05-08T12:11:19.197 回答
11

使用 malloc,您可以扩大和缩小您的数组:它变成动态的,因此您可以根据需要准确分配。

于 2009-05-08T10:56:41.817 回答
7

我猜这叫做自定义内存管理。你可以这样做,但你必须自己管理那块内存。你最终会编写自己的 malloc() 来处理这个块。

于 2009-05-08T10:54:28.890 回答
3

关于:

经过反复试验,我发现上面是我的 32 位 Intel 机器上允许使用 GCC 4.3 的最大静态数组。这是标准限制、编译器限制还是机器限制?

一个上限取决于 4GB(32 位)虚拟地址空间在用户空间和内核空间之间的分区方式。对于 Linux,我相信最常见的分区方案有 3 GB 的用户空间地址范围和 1 GB 的内核空间地址范围。分区可在内核构建时配置,2GB/2GB 和 1GB/3GB 分割也在使用中。加载可执行文件时,必须为每个对象分配虚拟地址空间,无论是否分配了实际内存来备份它。

于 2009-05-08T12:01:01.280 回答
3

您可能能够在一个上下文中分配那个巨大的数组,但不能在其他上下文中分配。例如,如果您的数组是结构的成员,并且您希望传递该结构。某些环境对结构大小有 32K 的限制。

如前所述,您还可以调整内存大小以完全使用您需要的内存。在性能关键的上下文中,如果可以避免分页到虚拟内存,这一点很重要。

于 2009-05-08T12:04:53.143 回答
3

除了超出范围之外,没有其他方法可以释放堆栈分配。因此,当您实际使用全局分配并且 VM 必须为您分配真正的硬内存时,它会被分配并保留在那里直到您的程序用完。这意味着任何进程只会在它的虚拟内存使用中增长(函数具有本地堆栈分配,这些将被“释放”)。

一旦超出功能范围,您就不能“保留”堆栈内存,它总是被释放。因此,您必须知道在编译时将使用多少内存。

然后归结为您可以拥有多少个 int foo[1<<29]。由于第一个占用了整个内存(在 32 位上)并且将是(让我们说谎:0x000000),第二个将解析为 0xffffffff 或大约。那么第三个会解决什么问题?32 位指针无法表达的东西。(请记住,堆栈保留部分在编译时解决,部分在运行时通过偏移量解决,当您分配这个或那个变量时堆栈偏移量被推送多远)。

所以答案几乎是,一旦你有了 int foo [1<<29] ,你就不能再有任何合理的函数深度和其他局部堆栈变量了。

于 2009-05-08T12:09:16.080 回答
2

你真的应该避免这样做,除非你知道你在做什么。尽量只请求所需的内存。即使它没有被使用或妨碍其他程序,它也可能会弄乱它自己的过程。有两个原因。首先,在某些系统上,尤其是 32 位系统上,它会在极少数情况下导致地址空间过早耗尽。此外,许多内核对保留/虚拟/未使用内存有某种每个进程的限制。如果您的程序在运行时请求内存,如果内核请求保留的内存超过此限制,则内核可以终止该进程。我见过由于 malloc 失败而崩溃或退出的程序,因为它们在只使用几 MB 的同时保留 GB 的内存。

于 2009-05-08T12:06:56.287 回答