![](/img/trans.png)
[英]Why is malloc() allocating more memory than demanded and how can I disable malloc from doing this in Mac OS X?
[英]Allocating more memory than there exists using malloc
此代码片段每次从 stdin 读取字母“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 ram 的 linux 虚拟机上运行此代码。 在使用htop工具监控系统资源使用情况时,发现malloc操作并没有反映在资源上。
例如,当我只输入 'u' 一次(即分配 2GB 的堆内存)时,我没有看到 htop 中的内存使用量增加了 2GB。 只有当我输入'a'(即初始化)时,我才看到内存使用量增加。
因此,我能够“分配”比现有更多的堆内存。 例如,我可以 malloc 6GB(这比我的 ram 和交换内存多)并且 malloc 会允许它(即 malloc 不返回 NULL)。 但是当我尝试初始化分配的内存时,我可以看到内存和交换内存被填满,直到进程被终止。
- 我的问题:
1.这是内核错误吗?
2.有人可以向我解释为什么允许这种行为吗?
来自man malloc
(在这里在线):
默认情况下,Linux 遵循乐观内存分配策略。 这意味着当 malloc() 返回非 NULL 时,不能保证内存确实可用。
因此,当您只想分配太多内存时,它“对您说谎”,当您想使用分配的内存时,它会尝试为您找到足够的内存,如果找不到足够的内存,它可能会崩溃。
不,这不是内核错误。 您发现了一种称为延迟分页(或过量使用)的东西。
在您向使用malloc (...)
分配的地址写入一个字节之前,内核所做的只是“保留”地址范围。 当然,这确实取决于内存分配器和操作系统的实现,但大多数好的内存分配器在首次使用内存之前不会产生大部分内核开销。
囤积分配器是一个立即想到的大罪犯,通过广泛的测试,我发现它几乎从不利用支持延迟分页的内核。 如果在分配后立即将整个内存范围填零,则始终可以减轻任何分配器中后期分页的影响。
像 VxWorks 这样的实时操作系统永远不会允许这种行为,因为延迟分页会导致严重的延迟。 从技术上讲,它所做的只是将延迟推迟到以后的不确定时间。
这是 Basile 提到的过度提交内存的结果。 然而,解释有点有趣。
基本上,当您尝试在 Linux (POSIX?) 中映射额外的内存时,内核只会保留它,并且只有在您的应用程序访问保留页面之一时才会实际使用它。 这允许多个应用程序保留比实际总量更多的 ram/swap。
这在大多数 Linux 环境中都是可取的行为,除非您有一个实时操作系统或您确切知道谁需要什么资源、何时以及为什么需要的东西。
否则有人可能会出现,将所有内存分配给内存(实际上并没有对其进行任何操作)并 OOM 应用程序。
这种懒惰分配的另一个例子是 mmap(),其中您有一个虚拟映射,您正在映射的文件可以放入其中 - 但您只有少量的实际内存专用于这项工作。 这允许您 mmap() 大文件(大于可用 RAM),并像使用普通文件句柄一样使用它们,这很漂亮)
-n
初始化/使用内存应该可以工作:
memset(m, 0, bytes);
您也可以使用calloc
,它不仅可以分配内存,还可以为您填充零:
char* m = (char*) calloc(1, bytes);
1.这是内核错误吗?
不。
2.有人可以向我解释为什么允许这种行为吗?
有几个原因:
减轻了解最终内存需求的需要- 让应用程序能够使用它认为可能实际需要的上限的内存量通常很方便。 例如,如果它正在准备某种类型的报告,无论是初始传递只是为了计算报告的最终大小,还是连续较大区域的 realloc()(具有必须复制的风险)可能会显着使代码复杂化并损害性能,其中将每个条目的某个最大长度乘以条目数可能非常快速和容易。 如果您知道就您的应用程序的需求而言,虚拟内存相对充足,那么分配更大的虚拟地址空间是非常便宜的。
稀疏数据——如果你有空闲的虚拟地址空间,能够有一个稀疏数组并使用直接索引,或者分配一个容量()与大小()比率很大的哈希表,可以导致一个非常高性能的系统。 当数据元素大小是内存分页大小的倍数或失败时,两者都工作得最好(在具有低开销/浪费和有效使用内存缓存的意义上)。
资源共享- 考虑一个 ISP 为建筑物中的 1000 名消费者提供“每秒 1 千兆位”的连接 - 他们知道如果所有消费者同时使用它,他们将获得大约 1 兆位,但依赖于他们的实际 -世界经验表明,尽管人们要求 1 Gb 并希望在特定时间获得很大一部分,但不可避免地存在一些较低的最大值和并发使用的平均值低得多的情况。 应用于内存的相同见解允许操作系统支持比其他方式更多的应用程序,并在满足预期方面取得合理的平均成功。 与共享 Internet 连接的速度随着更多用户同时提出需求而降低一样,从磁盘上的交换内存进行分页可能会启动并降低性能。 但与 Internet 连接不同的是,交换内存是有限制的,如果所有应用程序确实尝试同时使用内存以致超出该限制,则有些应用程序将开始收到报告内存耗尽的信号/中断/陷阱。 总而言之,在启用这种内存过量使用的情况下,仅检查malloc()
/ new
返回的非空指针不足以保证物理内存实际可用,并且程序稍后在尝试使用内存时可能仍会收到信号.
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.