如何处理 malloc 失败并返回 NULL?
How to handle malloc failing and returning NULL?
我对如何检查内存分配是否失败以防止由解除引用的 NULL
指针引起的任何未定义行为感到有点困惑。
我知道 malloc
(和类似的函数)可能会失败并且 return NULL
,因此,在继续执行其余部分之前,应始终检查地址 returned该程序。我不明白的是处理此类案件的最佳方式是什么。换句话说:当 malloc
调用 returns NULL
时程序应该做什么?
提出这个疑问时,我正在研究双向链表的实现。
struct ListNode {
struct ListNode* previous;
struct ListNode* next;
void* object;
};
struct ListNode* newListNode(void* object) {
struct ListNode* self = malloc(sizeof(*self));
if(self != NULL) {
self->next = NULL;
self->previous = NULL;
self->object = object;
}
return self;
}
节点的初始化只有在它的指针被正确分配时才会发生。如果这没有发生,这个构造函数 returns NULL
.
我还编写了一个函数,它从一个已经存在的节点开始创建一个新节点(调用 newListNode
函数),然后 return 创建它。
struct ListNode* createNextNode(struct ListNode* self, void* object) {
struct ListNode* newNext = newListNode(object);
if(newNext != NULL) {
newNext->previous = self;
struct ListNode* oldNext = self->next;
self->next = newNext;
if(oldNext != NULL) {
newNext->next = oldNext;
oldNext->previous = self->next;
}
}
return newNext;
}
If newListNode
returns NULL
, createNextNode
以及 returns NULL
并且传递给函数的节点没有'不要被感动。
然后使用ListNode结构体来实现实际的链表。
struct LinkedList {
struct ListNode* first;
struct ListNode* last;
unsigned int length;
};
_Bool addToLinkedList(struct LinkedList* self, void* object) {
struct ListNode* newNode;
if(self->length == 0) {
newNode = newListNode(object);
self->first = newNode;
}
else {
newNode = createNextNode(self->last, object);
}
if(newNode != NULL) {
self->last = newNode;
self->length++;
}
return newNode != NULL;
}
如果创建新节点失败,addToLinkedList
函数returns 0和链表本身保持不变。
最后,让我们考虑最后一个函数,它将一个链表的所有元素添加到另一个链表。
void addAllToLinkedList(struct LinkedList* self, const struct LinkedList* other) {
struct ListNode* node = other->first;
while(node != NULL) {
addToLinkedList(self, node->object);
node = node->next;
}
}
我应该如何处理 addToLinkedList
可能 return 0 的可能性?对于我收集到的信息,malloc
在不再可能分配内存时失败,所以我假设分配失败后的后续调用也会失败,对吗?那么,如果 0 被 returned,循环是否应该立即停止,因为无论如何都不可能向列表中添加任何新元素?
另外,按照我的方式将所有这些检查一个接一个地堆叠是否正确?不是多余的吗?一旦 malloc 失败就立即终止程序是否错误?我读到这对多线程程序来说会有问题,而且在某些情况下,程序可能能够继续 运行 而无需进一步分配内存,因此将其视为致命错误是错误的在任何可能的情况下。这样对吗?
抱歉拖了这么久post,感谢您的帮助!
这取决于更广泛的情况。对于某些程序,简单地中止是正确的做法。
对于某些应用程序,正确的做法是缩小缓存并再次尝试 malloc
。对于一些多线程程序,只需等待(让其他线程有机会释放内存)并重试即可。
对于需要高可靠的应用,你需要一个应用级的解决方案。我使用并经过实战测试的一种解决方案是:
- 在启动时分配一个紧急内存池。
- 如果
malloc
失败,释放一些紧急池。
- 对于无法正常处理
NULL
响应的调用,请休眠并重试。
- 有一个试图重新填充紧急池的服务线程。
- 让使用缓存的代码通过减少内存消耗来响应未满的紧急池。
- 如果您有能力减轻负载,例如,通过将负载转移到其他实例,请在紧急池未满时这样做。
- 对于需要分配大量内存的自主操作,请检查紧急池的级别,如果未满或接近满则不要执行该操作。
- 如果紧急池变空,则中止。
How to handle malloc failing and returning NULL?
考虑代码是一组助手 functions/library 还是应用程序。
终止决定最好由更高级别的代码处理。
示例:除了 exit()
、abort()
和朋友,标准 C 库不会退出。
同样 returning error codes/values 也是 OP 低级函数集的合理解决方案。即使对于 addAllToLinkedList()
,我也会考虑在 return 代码中传播错误。 (非零是一些错误。)
// void addAllToLinkedList(struct LinkedList* self, const struct LinkedList* other) {
int addAllToLinkedList(struct LinkedList* self, const struct LinkedList* other) {
...
if (addToLinkedList(self, node->object) == NULL) {
// Do some house-keepeing (undo prior allocations)
return -1;
}
对于更高级别的应用程序,请按照您的设计进行。现在,退出并显示失败消息可能很简单。
if (addAllToLinkedList(self, ptrs)) {
fprintf(stderr, "Linked List failure in %s %u\n", __func__, __LINE__);
exit(EXIT_FAILURE);
}
不退出示例:
考虑一个例程,该例程将文件读入数据结构并多次使用 LinkedList
并且文件以某种方式损坏导致内存分配过多。代码可能想要简单地释放该文件的所有内容(但只是该文件),并简单地向用户报告 "invalid file/out-of-memory" - 并继续 运行.
if (addAllToLinkedList(self, ptrs)) {
free_file_to_struct_resouces(handle);
return oops;
}
...
return success;
带走
低级例程指示错误不知何故。如果需要,更高级别的例程可以退出代码。
我对如何检查内存分配是否失败以防止由解除引用的 NULL
指针引起的任何未定义行为感到有点困惑。
我知道 malloc
(和类似的函数)可能会失败并且 return NULL
,因此,在继续执行其余部分之前,应始终检查地址 returned该程序。我不明白的是处理此类案件的最佳方式是什么。换句话说:当 malloc
调用 returns NULL
时程序应该做什么?
提出这个疑问时,我正在研究双向链表的实现。
struct ListNode {
struct ListNode* previous;
struct ListNode* next;
void* object;
};
struct ListNode* newListNode(void* object) {
struct ListNode* self = malloc(sizeof(*self));
if(self != NULL) {
self->next = NULL;
self->previous = NULL;
self->object = object;
}
return self;
}
节点的初始化只有在它的指针被正确分配时才会发生。如果这没有发生,这个构造函数 returns NULL
.
我还编写了一个函数,它从一个已经存在的节点开始创建一个新节点(调用 newListNode
函数),然后 return 创建它。
struct ListNode* createNextNode(struct ListNode* self, void* object) {
struct ListNode* newNext = newListNode(object);
if(newNext != NULL) {
newNext->previous = self;
struct ListNode* oldNext = self->next;
self->next = newNext;
if(oldNext != NULL) {
newNext->next = oldNext;
oldNext->previous = self->next;
}
}
return newNext;
}
If newListNode
returns NULL
, createNextNode
以及 returns NULL
并且传递给函数的节点没有'不要被感动。
然后使用ListNode结构体来实现实际的链表。
struct LinkedList {
struct ListNode* first;
struct ListNode* last;
unsigned int length;
};
_Bool addToLinkedList(struct LinkedList* self, void* object) {
struct ListNode* newNode;
if(self->length == 0) {
newNode = newListNode(object);
self->first = newNode;
}
else {
newNode = createNextNode(self->last, object);
}
if(newNode != NULL) {
self->last = newNode;
self->length++;
}
return newNode != NULL;
}
如果创建新节点失败,addToLinkedList
函数returns 0和链表本身保持不变。
最后,让我们考虑最后一个函数,它将一个链表的所有元素添加到另一个链表。
void addAllToLinkedList(struct LinkedList* self, const struct LinkedList* other) {
struct ListNode* node = other->first;
while(node != NULL) {
addToLinkedList(self, node->object);
node = node->next;
}
}
我应该如何处理 addToLinkedList
可能 return 0 的可能性?对于我收集到的信息,malloc
在不再可能分配内存时失败,所以我假设分配失败后的后续调用也会失败,对吗?那么,如果 0 被 returned,循环是否应该立即停止,因为无论如何都不可能向列表中添加任何新元素?
另外,按照我的方式将所有这些检查一个接一个地堆叠是否正确?不是多余的吗?一旦 malloc 失败就立即终止程序是否错误?我读到这对多线程程序来说会有问题,而且在某些情况下,程序可能能够继续 运行 而无需进一步分配内存,因此将其视为致命错误是错误的在任何可能的情况下。这样对吗?
抱歉拖了这么久post,感谢您的帮助!
这取决于更广泛的情况。对于某些程序,简单地中止是正确的做法。
对于某些应用程序,正确的做法是缩小缓存并再次尝试 malloc
。对于一些多线程程序,只需等待(让其他线程有机会释放内存)并重试即可。
对于需要高可靠的应用,你需要一个应用级的解决方案。我使用并经过实战测试的一种解决方案是:
- 在启动时分配一个紧急内存池。
- 如果
malloc
失败,释放一些紧急池。 - 对于无法正常处理
NULL
响应的调用,请休眠并重试。 - 有一个试图重新填充紧急池的服务线程。
- 让使用缓存的代码通过减少内存消耗来响应未满的紧急池。
- 如果您有能力减轻负载,例如,通过将负载转移到其他实例,请在紧急池未满时这样做。
- 对于需要分配大量内存的自主操作,请检查紧急池的级别,如果未满或接近满则不要执行该操作。
- 如果紧急池变空,则中止。
How to handle malloc failing and returning NULL?
考虑代码是一组助手 functions/library 还是应用程序。
终止决定最好由更高级别的代码处理。
示例:除了 exit()
、abort()
和朋友,标准 C 库不会退出。
同样 returning error codes/values 也是 OP 低级函数集的合理解决方案。即使对于 addAllToLinkedList()
,我也会考虑在 return 代码中传播错误。 (非零是一些错误。)
// void addAllToLinkedList(struct LinkedList* self, const struct LinkedList* other) {
int addAllToLinkedList(struct LinkedList* self, const struct LinkedList* other) {
...
if (addToLinkedList(self, node->object) == NULL) {
// Do some house-keepeing (undo prior allocations)
return -1;
}
对于更高级别的应用程序,请按照您的设计进行。现在,退出并显示失败消息可能很简单。
if (addAllToLinkedList(self, ptrs)) {
fprintf(stderr, "Linked List failure in %s %u\n", __func__, __LINE__);
exit(EXIT_FAILURE);
}
不退出示例:
考虑一个例程,该例程将文件读入数据结构并多次使用 LinkedList
并且文件以某种方式损坏导致内存分配过多。代码可能想要简单地释放该文件的所有内容(但只是该文件),并简单地向用户报告 "invalid file/out-of-memory" - 并继续 运行.
if (addAllToLinkedList(self, ptrs)) {
free_file_to_struct_resouces(handle);
return oops;
}
...
return success;
带走
低级例程指示错误不知何故。如果需要,更高级别的例程可以退出代码。