Как обработать ошибку mallo c и вернуть NULL? - PullRequest
4 голосов
/ 18 марта 2020

Я немного запутался в том, как проверить, не удалось ли выделить память, чтобы предотвратить любое неопределенное поведение, вызванное разыменованным указателем 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;
}

Инициализация узла происходит только в том случае, если его указатель был правильно размещен. Если этого не произошло, эта функция конструктора возвращает NULL.

Я также написал функцию, которая создает новый узел (вызывая функцию newListNode), начиная с уже существующего узла, а затем возвращает it.

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;
}

Если newListNode возвращает NULL, createNextNode также возвращает 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 возвращает 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 может вернуть 0? Для того, что я собрал, malloc терпит неудачу, когда больше невозможно выделить память, поэтому я предполагаю, что последующие вызовы после сбоя выделения также будут неудачными, я прав? Таким образом, если возвращается 0, должен ли l oop немедленно прекратиться, поскольку в любом случае невозможно будет добавить какие-либо новые элементы в список? Кроме того, правильно ли складывать все эти проверки друг над другом так, как я это сделал? Разве это не избыточно? Было бы неправильно просто немедленно прекратить программу, как только ошибка mallo c? Я прочитал, что это будет проблематично c для многопоточных программ, а также, что в некоторых случаях программа может продолжать работать без дальнейшего выделения памяти, поэтому было бы неправильно рассматривать это как фатальную ошибку в любой возможный случай. Это правильно?

Извините за действительно длинный пост и спасибо за вашу помощь!

Ответы [ 2 ]

6 голосов
/ 18 марта 2020

Это зависит от более широких обстоятельств. Для некоторых программ просто прерывание - это правильное действие.

Для некоторых приложений правильное решение - сократить кэши и повторить попытку malloc. Для некоторых многопоточных программ достаточно просто подождать (чтобы дать другим потокам возможность освободить память) и повторить попытку.

Для приложений, которые должны быть высоконадежными, вам необходимо решение на уровне приложений. Одно из решений, которое я использовал и проверял в бою, таково:

  1. Имейте резервный пул памяти, выделенный при запуске.
  2. Если malloc не удается, освободите часть аварийного пула .
  3. Для вызовов, которые не могут нормально обрабатывать ответ NULL, перевести в спящий режим и повторить попытку.
  4. Есть служебный поток, который пытается пополнить резервный пул.
  5. Есть код, использующий кэширование, реагирует на неполный аварийный пул, уменьшая потребление памяти.
  6. Если у вас есть возможность сбросить нагрузку, например, путем переноса нагрузки на другие экземпляры, сделайте это, если аварийный пул не ' t full.
  7. Для произвольных действий, требующих выделения большого объема памяти, проверьте уровень аварийного пула и не выполняйте действия, если он не заполнен или близок к нему.
  8. Если аварийный пул пуст, отмена.
2 голосов
/ 18 марта 2020

Как справиться с ошибкой mallo c и возвратом NULL?

Рассмотрим, является ли код набором вспомогательных функций / библиотеки или приложения.

Решение Завершение лучше всего обрабатывается кодом более высокого уровня.

Пример: кроме exit(), abort() и друзей, стандартная библиотека C не закрывается.

Аналогичным образом, возврат кодов / значений ошибок является разумным решением и для низкоуровневых наборов функций OP. Даже для addAllToLinkedList() я бы посоветовал распространить ошибку в коде возврата. (Ненулевое значение - это некоторая ошибка.)

// 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 и файл был каким-то образом поврежден, что привело к чрезмерному выделению памяти. Код может захотеть просто освободить все для этого файла (но только для этого файла) и просто сообщить пользователю «неверный файл / недостаточно памяти» - и продолжить работу.

if (addAllToLinkedList(self, ptrs)) {
  free_file_to_struct_resouces(handle);
  return oops;
}
...
return success;

Забрать

Низкоуровневые процедуры указывают на ошибку как-то . При желании процедуры более высокого уровня могут выйти из кода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...