如何处理 malloc 失败并返回 NULL?

Gia*_*ian 4 c memory malloc memory-management out-of-memory

我对如何检查内存分配是否失败以防止由取消引用的NULL指针引起的任何未定义行为感到有些困惑。我知道malloc(和类似的函数)可能会失败并返回NULL,因此在继续执行程序的其余部分之前,应始终检查返回的地址。我不明白的是处理此类案件的最佳方法是什么。换句话说:当malloc调用返回时程序应该做什么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;
}
Run Code Online (Sandbox Code Playgroud)

节点的初始化仅在其指针被正确分配时发生。如果这没有发生,则此构造函数返回NULL

我还编写了一个函数,该newListNode函数从一个已经存在的节点开始创建一个新节点(调用该函数),然后返回它。

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;
}
Run Code Online (Sandbox Code Playgroud)

如果newListNode返回NULLcreateNextNode也返回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;
}
Run Code Online (Sandbox Code Playgroud)

如果新节点的创建失败,该addToLinkedList函数返回 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我应该如何处理addToLinkedList可能返回 0的可能性?对于我收集的内容,malloc当不再可能分配内存时失败,所以我假设分配失败后的后续调用也会失败,对吗?那么,如果返回 0,循环是否应该立即停止,因为无论如何都不可能向列表添加任何新元素?另外,按照我的方式将所有这些支票叠在一起是否正确?不是多余的吗?一旦 malloc 失败就立即终止程序是错误的吗?我读到这对于多线程程序来说是有问题的,而且在某些情况下,程序可能无需进一步分配内存就可以继续运行,因此在任何可能的情况下将其视为致命错误是错误的. 这是正确的吗?

很抱歉这篇很长的帖子,感谢您的帮助!

Dav*_*rtz 6

这取决于更广泛的情况。对于某些程序,简单地中止是正确的做法。

对于某些应用程序,正确的做法是缩小缓存并重试malloc。对于某些多线程程序,只需等待(让其他线程有机会释放内存)并重试即可。

对于需要高度可靠的应用程序,您需要一个应用程序级解决方案。我使用过并经过实战测试的一种解决方案是:

  1. 在启动时分配一个紧急内存池。
  2. 如果malloc失败,释放一些应急池。
  3. 对于无法正常处理NULL响应的调用,休眠并重试。
  4. 有一个服务线程尝试重新填充应急池。
  5. 让使用缓存的代码通过减少内存消耗来响应未满的紧急池。
  6. 如果您有能力减轻负载,例如,通过将负载转移到其他实例,请在应急池未满时这样做。
  7. 对于需要分配大量内存的任意操作,请检查紧急池的级别,如果它未满或接近它,则不要执行该操作。
  8. 如果应急池变空,则中止。

  • “永远重试”让我担心——YMMV。 (3认同)