在C中,如何知道动态分配是否成功

2021-11-06 22:59:43 浏览数 (1)

mallco是分配虚拟内存

C语言使用 malloc函数动态在堆上分配内存。malloc根据字节数的参数。如果无法分配内存,该函数将返回指向已分配内存的指针或 NULL 指针。

下面一个程序,分配 1 TB 的内存,然后在这个新分配的内存尝试写入:

代码语言:javascript复制
#include <stdio.h>
#include <stdlib.h>

int main() {
  size_t large = 1099511627776;
  char *buffer = (char *)malloc(large);
  if (buffer == NULL) {
    printf("error!n");
    return EXIT_FAILURE;
  }
  printf("Memory allocatedn");
  for (size_t i = 0; i < large; i  = 4096) {
    buffer[i] = 0;
  }
  free(buffer);
  return EXIT_SUCCESS;
}

运行和编译此程序,可能会收到消息“error!”,在这种情况下,程序会立即终止……否则可能会看到“Memory allocated”(如果有 1 TB 的内存可分配),

在 macOS/clang 和 Linux/GCC 下,有时候会打印“Memory allocated”然后崩溃。

malloc 调用确实分配了内存,但它会分配“虚拟内存”。可能根本没有分配物理内存。系统只是为内存分配留出地址空间。当尝试使用内存时,就会发生物理分配。然后它可能会失败。

当询问程序使用多少内存时,对 malloc 的调用相加是错误的,因为这是虚拟内存使用量。

process

memory (virtual)

memory (real)

qemu

3.94 GB

32 MB

safari

3.7 GB

180 MB

动态内存按页(例如,4 kB)分配,通常比虚拟内存小得多。执行“malloc(x)”与占用 x 字节的物理内存不同。因此,依靠 malloc 确定分配是否成功是一个困难的问题。只有在写入和读取新分配的内存时才能发现。


设置是否开启过量内存

通过 /proc/sys/vm/overcommit_memory查看是否支持过量内存。Windows 不允许过量使用(但仍使用相同的虚拟/物理内存设计)。Linux 有 3 种过量使用模式,启发式(默认)、始终和从不。

https://www.kernel.org/doc/Documentation/vm/overcommit-accounting

如果 /proc/sys/vm/overcommit_memory 为0,则进程退出并显示“error!”;如果是 1,则该进程在一段时间后被 OOM 杀手终止(我的笔记本电脑没有 1T内存),通常将 /proc/sys/vm/overcommit_memory 设置为0。


mmap和mlock操作物理内存

如果要分配物理内存,请使用 mmap()(带选项的 malloc)分配地址空间,并使用 mlock() 将物理页连接到进程中的地址。如果没有足够的物理内存来满足您的请求,mlock() 将失败。


嵌入式为什么不执行malloc

这就是为什么某些嵌入式系统不执行 malloc 的原因。嵌入式系统(那些不允许 malloc 的系统)由于没有 MMU 通常没有虚拟内存,所以在那些你不能过度使用的系统上,因为没有页面错误机制。

原因很简单,通过静态分配所有内存,可以避免整个类的程序错误。没有内存泄漏,不需要解决“是否存在动态内存分配将失败的执行路径”的 NP 完全问题。它不仅与动态分配的内存总量有关,还与分配(和释放)的顺序有关。


程序可以分配比服务器上物理可用内存更多的内存吗

一个面试问题是“程序可以分配比服务器上物理可用内存更多的内存吗?”这是希望通过它了解面试者对操作系统和虚拟内存的了解程度。

“程序可以~~分配malloc~~使用比服务器上物理可用更多的内存(假设没有交换)?” 因为, malloc 从虚拟内存中分配,而不是从物理内存中分配。只有第一次通过读/写显式访问内存时,才会发生页面错误并开始页面分配。如果无法分配页面,则程序会以 SIGNAL 终止。这里,malloc 成功,因为从 VM 分配成功。但这并不能保证拥有所有的内存。即使在程序开始时分配了所有内容,仍然可能会耗尽内存......这是不可预测的。


Linux的OOM

程序很可能在 Linux 上被 OOM 杀死了。或者使用 mmap & mlock 来验证分配是否成功,但该进程仍然可以随时因任何原因被 OOM 杀死。

在 macOS 上也是如此。VM 压缩器(内核内和磁盘上压缩的“段”组合)有 64 个 gig 的限制;当达到这一点时,拥有超过 50% 压缩内存的进程可以被杀死。

参见 no_paging_space_action() :


存在过量使用的最大原因

Linux 和 macOS 上存在过量使用的最大原因:fork()。当进程分叉时,由于写时复制,绝大多数子进程的内存与父进程安全共享。但是严格的计算会说系统的总内存使用量翻了一番,这在大多数情况下太保守了。由于fork在 Unix 上非常普遍,因此很快就需要过度使用。否则,fork/exec 将停止在任何使用超过一半系统内存的进程中工作。

这就是 Linux 所做的。当复制COW 页面确实发生并且现在系统内存不足时,返回 ENOMEM 呢。内存写入不返回错误代码。OOM killer发送一个信号。

这就是为什么您要确保有足够的Swap分区来应对最坏的情况。使用Swap分区不是因为实际使用它,而是为了能够保证在最坏的情况发生时有足够的内存可用。在正常情况下,永远不应该真正使用Swap分区。

对于使用它们的每个进程,共享库可能会同时计入实内存和虚拟内存中,即使它们占用相同页面的只读或写时复制内存,并且内存映射文件可能会被全部计入在虚拟内存中,即使只有一小部分文件被读取,并且在 Linux 上,内存不足killer可能会在进程尝试真正访问过度分配的虚拟内存时选择杀死一个*不同的*进程,并且C 共享库可能不会*真正* 释放 free() 的内存,因为在下次尝试 malloc() 时保留它以避免访问内核会更快,并且这些东西都不是在标准中一成不变的,这一切都可能已经过时了几年......

没有Swap意味着只能使用驱动磁盘文件支持的页面。在内存争用期间,这可能会导致抖动。在“正常”操作期间,它会降低性能。仅在内存用完时才使用Swap分区,是一个非常普遍的误解。

0 人点赞