阅读Martin Sustrick关于在C++和C中防止"未定义行为"所带来的挑战的博客,特别是malloc()由于内存耗尽而失败的问题,我想起了在这种情况下我曾多次感到沮丧,不知道该怎么办。
在虚拟系统中,这种情况很少见,但在嵌入式平台上,或者在与虚拟系统相关的性能下降等同于故障的情况下,就像Martin在ZeroMQ中的情况一样,我决心找到一个可行的解决方案,并做到了。
我想问StackOverflow的读者是否尝试过这种方法,以及他们的经验
解决方案是在程序开始时通过调用malloc()从堆中分配一块空闲内存,然后在发生内存耗尽时使用该空闲内存池来避免内存耗尽。这个想法是为了防止投降,支持有序撤退(我昨晚读到了凯塞林保卫意大利的报道),在那里,错误消息和IP套接字等将工作足够长的时间,(希望)至少可以告诉用户发生了什么。
#define SPARE_MEM_SIZE (1<<20) // reserve a megabyte
static void *gSpareMem;
// ------------------------------------------------------------------------------------------------
void *tenacious_malloc(int requested_allocation_size) {
static int remaining_spare_size = 0; // SPARE_MEM_SIZE;
char err_msg[512];
void *rtn = NULL;
// attempt to re-establish the full size of spare memory, if it needs it
if (SPARE_MEM_SIZE != remaining_spare_size) {
if(NULL != (gSpareMem = realloc(gSpareMem, SPARE_MEM_SIZE))) {
remaining_spare_size = SPARE_MEM_SIZE;
// "touch" the memory so O/S will allocate physical memory
meset(gSpareMem, 0, SPARE_MEM_SIZE);
printf("nSize of spare memory pool restored successfully in %s:%s at line %i :)n",
__FILE__, __FUNCTION__, __LINE__);
} else {
printf("nUnable to restore size of spare memory buffer.n");
}
}
// attempt a plain, old vanilla malloc() and test for failure
if(NULL != (rtn = malloc(requested_allocation_size))) {
return rtn;
} else {
sprintf(err_msg, "nInitial call to malloc() failed in %s:%s at line %i",
__FILE__, __FUNCTION__, __LINE__);
if(remaining_spare_size < requested_allocation_size) {
// not enough spare storage to satisfy the request, so no point in trying
printf("%snRequested allocaton larger than remaining pool. :(nt --- ABORTING --- n", err_msg);
return NULL;
} else {
// take the needed storage from spare memory
printf("%snRetrying memory allocation....n", err_msg);
remaining_spare_size -= requested_allocation_size;
if(NULL != (gSpareMem = realloc(gSpareMem, remaining_spare_size))) {
// return malloc(requested_allocation_size);
if(NULL != (rtn = malloc(requested_allocation_size))) {
printf("Allocation from spare pool succeeded in %s:%s at line %i :)n",
__FILE__, __FUNCTION__, __LINE__);
return rtn;
} else {
remaining_spare_size += requested_allocation_size;
sprintf(err_msg, "nRetry of malloc() after realloc() of spare memory pool "
"failed in %s:%s at line %i :(n", __FILE__, __FUNCTION__, __LINE__);
return NULL;
}
} else {
printf("nRetry failed.nUnable to allocate requested memory from spare pool. :(n");
return NULL;
}
}
}
}
// ------------------------------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[]) {
int *IntVec = NULL;
double *DblVec = NULL;
char *pString = NULL;
char String[] = "Every good boy does fine!";
IntVec = (int *) tenacious_malloc(100 * sizeof(int));
DblVec = (double *) tenacious_malloc(100 * sizeof(double));
pString = (char *)tenacious_malloc(100 * sizeof(String));
strcpy(pString, String);
printf("n%s", pString);
printf("nHit Enter to end program.");
getchar();
return 0;
}
最佳策略是针对无需分配即可工作的代码。特别是,对于一个正确、健壮的程序,所有故障路径都必须是无故障的,这意味着您不能在故障路径中使用分配。
只要可能,我的偏好是在操作开始后避免任何分配,而是在操作开始前确定所需的存储并将其全部分配。这可以极大地简化程序逻辑,并使测试变得更加容易(因为只有一个可能的失败点需要测试)。当然,它在其他方面也可能更贵;例如,您可能需要对输入数据进行两次检查,以确定需要多少存储,然后使用存储进行处理。
关于在malloc
出现故障时预先分配一些紧急存储使用的解决方案,基本上有两个版本:
- 只需调用紧急存储器上的
free
,然后希望malloc
再次工作 - 遍历您自己的包装层,以获取包装层可以直接使用紧急存储而不需要释放它的所有内容
第一种方法的优点是,即使是标准库和第三方库代码也可以利用紧急空间,但它的缺点是,释放的存储空间可能会被其他进程或您自己进程中的线程窃取,从而竞相获取。如果你确信内存耗尽将来自于耗尽虚拟地址空间(或进程资源限制),而不是系统资源,并且你的进程是单线程的,那么你不必担心竞争,你可以相当安全地假设这种方法会奏效。然而,总的来说,第二种方法要安全得多,因为你有一个绝对的保证,你可以获得所需的紧急存储量。
我真的不喜欢这两种方法,但它们可能是你能做的最好的
在现代64位计算机上,您可以占用比RAM多得多的内存。在实践中,malloc不会失败。实际情况是,你的应用程序开始崩溃,一旦你有4GB的RAM,并且你的分配超过了这个值,你的性能就会降到零,因为你正在疯狂地交换。您的性能下降太多,以至于您永远不会达到malloc无法返回内存的地步。
"在现代64位计算机上,您可以分配比实际安装的RAM多得多的内存,例如通过调用malloc()
">
请注意,RAM并不等于虚拟内存,这就引出了下一点。
一个人是否可以获得比物理可用RAM更多的虚拟内存取决于操作系统:GNU/Linux操作系统通常会出现开箱即用的错误配置,从而过度配置内存,这在任何情况下都是不正确的行为,在关键任务部署中尤其有害。
所有传统的UNIX系统都不允许请求比可用内存更多的虚拟内存,并将返回ENOMEM
,这是正确的行为,因为这意味着没有像";失忆杀手";是必要的,这反过来意味着运行的进程将继续运行,前提是它们已经拥有所需的所有内存。