使用 malloc 分配比现有内存更多的内存
Posted
技术标签:
【中文标题】使用 malloc 分配比现有内存更多的内存【英文标题】:Allocating more memory than there exists using malloc 【发布时间】:2013-11-14 01:44:27 【问题描述】:这段代码 sn-p 将在每次从标准输入读取字母“u”时分配 2Gb,并在读取“a”后初始化所有分配的字符。
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <vector>
#define bytes 2147483648
using namespace std;
int main()
char input [1];
vector<char *> activate;
while(input[0] != 'q')
gets (input);
if(input[0] == 'u')
char *m = (char*)malloc(bytes);
if(m == NULL) cout << "cant allocate mem" << endl;
else cout << "ok" << endl;
activate.push_back(m);
else if(input[0] == 'a')
for(int x = 0; x < activate.size(); x++)
char *m;
m = activate[x];
for(unsigned x = 0; x < bytes; x++)
m[x] = 'a';
return 0;
我在具有 3Gb 内存的 linux 虚拟机上运行此代码。在使用 htop 工具监控系统资源使用情况时,我意识到 malloc 操作并没有反映在资源上。
例如,当我只输入 'u' 一次(即分配 2GB 堆内存)时,我看不到 htop 中的内存使用量增加了 2GB。只有当我输入“a”(即初始化)时,我才看到内存使用量增加。
因此,我能够“malloc”比现有更多的堆内存。例如,我可以 malloc 6GB(这比我的 ram 和交换内存多)并且 malloc 会允许它(即 malloc 不返回 NULL)。但是当我尝试初始化分配的内存时,我可以看到内存和交换内存已满,直到进程被杀死。
-我的问题:
1.这是内核错误吗?
2.有人可以向我解释为什么允许这种行为吗?
【问题讨论】:
顺便说一句,您对gets()
的调用会导致缓冲区溢出。解决办法就是,扔掉它。
你有undefined behavior。您不能确定未初始化的input[0]
在main
的开头不是q
,您很幸运。使用g++ -Wall
编译。
【参考方案1】:
它被称为memory overcommit。您可以通过以 root 身份运行来禁用它:
echo 2 > /proc/sys/vm/overcommit_memory
这不是我喜欢的内核特性(所以我总是禁用它)。见malloc(3)和mmap(2)和proc(5)
NB:echo 0
而不是echo 2
经常 - 但并非总是 - 也可以。阅读文档(特别是我刚刚链接到的 proc
手册页)。
【讨论】:
感谢指正。将 0 替换为 2。在实践中,通常 0 就足够了。【参考方案2】:来自man malloc
(online here):
默认情况下,Linux 遵循乐观的内存分配策略。 这意味着当 malloc() 返回非 NULL 时,不能保证 内存确实可用。
所以当你只想分配太多时,它会“欺骗”你,当你想使用分配的内存时,它会尝试为你找到足够的内存,如果找不到足够的内存,它可能会崩溃.
【讨论】:
【参考方案3】:这是 Basile 提到的过度提交内存的结果。但是,解释有点有趣。
基本上,当您尝试在 Linux(POSIX?)中映射额外的内存时,内核只会保留它,并且只有在您的应用程序访问其中一个保留页面时才会真正使用它。这允许多个应用程序保留超过实际总内存/交换量。
这在大多数 Linux 环境中都是可取的行为,除非您有一个实时操作系统或您确切知道谁需要什么资源、何时以及为什么需要的东西。
否则有人可能会出现,分配所有内存(实际上不对其进行任何操作)并 OOM 您的应用程序。
这种惰性分配的另一个例子是 mmap(),其中您有一个虚拟映射,您正在映射的文件可以放入其中 - 但您只有少量的实际内存专用于该工作。这允许您 mmap() 大文件(大于可用 RAM),并像使用普通文件句柄一样使用它们,这很漂亮)
-n
【讨论】:
这是可取行为的事实是见仁见智,我强烈反对;恕我直言,内存过度使用总是不好的(因为它隐藏了开发人员的错误)。 如果有人能澄清这种行为是仅限于 linux 还是在 POSIX 中,那就太好了 它是 Linux 特定的,而不是 POSIX。 感谢您的澄清!我忘记了这是一种 Linux 主义。我不同意过度承诺总是一件“坏事”。很容易看出您实际使用了多少内存与分配了多少内存。不要把婴儿和洗澡水一起扔出去。 我们仍然同意这是一个见仁见智的问题!而mmap
与MAP_SHARED
的真实文件不要使用过度提交(因为该文件用作“交换”)。【参考方案4】:
初始化/使用内存应该可以工作:
memset(m, 0, bytes);
您也可以使用calloc
,它不仅可以分配内存,还可以为您填充零:
char* m = (char*) calloc(1, bytes);
【讨论】:
【参考方案5】:不,这不是内核错误。您发现了一种称为延迟分页(或过度使用)的情况。
直到您将一个字节写入使用malloc (...)
分配的地址之前,内核所做的只是“保留”地址范围。当然,这实际上取决于您的内存分配器和操作系统的实现,但大多数好的分配器在首次使用内存之前不会产生大部分内核开销。
囤积分配器是一个立即浮现在脑海中的大问题,通过广泛的测试,我发现它几乎从不利用支持延迟分页的内核。如果在分配后立即对整个内存范围进行零填充,则始终可以减轻 any 分配器中延迟分页的影响。
像 VxWorks 这样的实时操作系统永远不会允许这种行为,因为延迟分页会引入严重的延迟。从技术上讲,它所做的只是将延迟推迟到以后的不确定时间。
如需更详细的讨论,您可能有兴趣了解 IBM 的 AIX 操作系统如何处理 page allocation 和 overcommitment。
【讨论】:
【参考方案6】:1.这是内核错误吗?
没有。
2.有人可以向我解释为什么允许这种行为吗?
有几个原因:
减少对最终内存需求的了解 - 让应用程序能够达到它认为上限的内存量通常很方便需要它实际上可能有。例如,如果它正在准备某种类型的报告,无论是初始传递只是为了计算报告的最终大小,还是连续更大区域的 realloc() (具有必须复制的风险)都可能会使代码显着复杂化并损害性能,其中,将每个条目的某个最大长度乘以条目数可能非常快速和容易。如果您知道就应用程序的需求而言虚拟内存相对充足,那么分配更大的虚拟地址空间非常便宜。
稀疏数据 - 如果你有空闲的虚拟地址空间,能够有一个稀疏数组并使用直接索引,或者分配一个哈希表慷慨的容量()与大小()的比例,可以导致一个非常高性能的系统。当数据元素大小是内存分页大小的倍数时,两者都工作得最好(在低开销/浪费和有效使用内存缓存的意义上),或者失败的更大或很小的整数部分。
资源共享 - 考虑一个 ISP 为建筑物中的 1000 个消费者提供“每秒 1 吉比特”的连接 - 他们知道,如果所有消费者同时使用它,他们将获得大约 1 兆位,但依赖于他们的实际经验,尽管人们要求 1 吉位并在特定时间想要其中的一小部分,但不可避免地会有一些较低的最大值和更多并发使用的平均值较低。应用于内存的相同见解使操作系统能够支持比其他方式更多的应用程序,并且在满足期望方面取得了合理的平均成功。就像共享 Internet 连接的速度会随着更多用户同时发出请求而降低一样,从磁盘上的交换内存进行分页可能会启动并降低性能。但与互联网连接不同的是,交换内存是有限制的,如果所有应用程序确实尝试同时使用内存以超过该限制,一些应用程序将开始收到报告内存耗尽的信号/中断/陷阱。总而言之,启用此内存过量使用行为后,仅检查 malloc()
/new
返回的非 NULL 指针不足以保证物理内存实际可用,并且程序稍后可能仍会在尝试使用时收到信号记忆。
【讨论】:
以上是关于使用 malloc 分配比现有内存更多的内存的主要内容,如果未能解决你的问题,请参考以下文章
为啥“任何 CPU(首选 32 位)”允许我在 .NET 4.5 下分配比 x86 更多的内存?