goa*_*ran 3 c++ stdvector bad-alloc
我有以下代码来分配大量数据,如果它超过可用内存(这里是 32GB),它应该抛出异常。使用:
bool MyObject::init()
{
char* emergency_memory = new char[32768];
try
{
std::vector<std::vector<MyData> > data_list;
std::vector<MyData> data;
data.resize(1000);
for(size_t i=0; i<1000; i++)
{
data_list.push_back(data);
data_list.push_back(data);
}
}
catch (const std::bad_alloc& e)
{
delete[] emergency_memory;
std::cout << "Data Allocation failed:" << e.what() << std::endl;
return false;
}
return true;
}
Run Code Online (Sandbox Code Playgroud)
异常永远不会被捕获。应用程序刚刚终止,或者使操作系统崩溃。
我做错了什么?
您的new
操作员必须从某个地方获取内存。由于new
用户空间代码与实际内存没有任何连接,它所能做的就是通过系统调用sbrk()
或系统调用mmap()
向内核请求一些内存。内核将通过将一些额外的内存页映射到进程的虚拟地址空间来做出响应。
事实上,内核返回给用户进程的任何内存页都必须清零。如果跳过此步骤,内核可能会将其他应用程序或其自身的敏感数据泄漏到用户空间进程。
而且内核总是有一个内存页只包含零。mmap()
因此,只需将这一个零页映射到新的地址范围,它就可以简单地满足任何请求。它将把这些映射标记为写时复制,这样每当用户空间进程开始写入这样的页面时,内核就会立即创建零页面的副本。然后内核将摸索另一页内存来支持它的承诺。
你看到问题了吗?在进程实际写入内存之前,内核不需要任何物理内存。这称为内存过度使用。当您派生一个进程时,会发生另一种情况。您认为当您调用时内核会立即复制您的内存fork()
吗?当然不是。它只会对现有内存页进行一些 COW 映射!
(这是一个重要的优化机制:许多启动的映射不需要额外的内存支持。这对于以下情况尤其重要fork()
:此调用通常紧接着一个exec()
调用,该调用将立即再次拆除 COW 映射。)
缺点是,内核永远不知道它实际需要多少物理内存,直到它无法兑现自己的承诺。这就是为什么当你用完内存时不能依赖sbrk()
或返回错误:在写入映射内存之前,你不会用完内存。系统调用没有返回错误代码意味着您的操作员不知道何时抛出。所以不会抛。mmap()
new
相反,当内核意识到内存不足时,它会出现恐慌,并开始关闭进程。这就是内存不足杀手的工作。这只是为了避免立即重新启动,并且,如果 OOM 杀手的启发式运行良好,它实际上会启动正确的进程。被杀死的进程不会收到警告,它们只是被信号终止。再次不涉及用户空间异常。
TL;DR:捕获bad_alloc
过度提交的内核上的异常几乎是没有用的。