Подтвердить что ты не робот

C API design: что делать, когда malloc возвращает NULL?

Скажем, я пишу небольшую библиотеку в C - какую-то структуру данных, скажем. Что делать, если я не могу выделить память?

Это может быть очень важно, например. Мне нужна память для инициализации структуры данных в первую очередь, или я вставляю пару "ключ-значение" и хочу обернуть ее в маленькую структуру. Это также может быть менее критичным, например, что-то вроде функции pretty_print, которая создает хорошее строковое представление содержимого. Тем не менее, он, как правило, более серьезный, чем ваша средняя ошибка - не может быть смысла продолжать вообще. Тонна выборки использует malloc в режиме онлайн прямо перед тем, как выйти из программы, если она возвращает NULL. Я предполагаю, что много реальных клиентских кодов тоже делает это - просто выскочите какую-либо ошибку или напишите ее на stderr и прервите. (И много реального кода, вероятно, вообще не проверяет возвращаемое значение malloc.)

Иногда имеет смысл возвращать NULL, но не всегда. Коды ошибок (или просто некоторые значения boolean success), либо как возвращаемые значения, либо параметры вывода работают нормально, но похоже, что они могут загромождать или ухудшать читаемость API (опять же, возможно, это несколько ожидалось на языке, таком как C?). Другой вариант - иметь какое-то внутреннее состояние ошибки, которое вызывающий может впоследствии запросить, например. с функцией get_error, но тогда вы должны быть осторожны в безопасности потоков, и это может быть легко пропустить; люди склонны быть слабыми в проверке ошибок в любом случае, и если это отдельная функция вообще, они могут не знать об этом, или они могут не беспокоиться (но тогда я предполагаю, что их проблема).

(Я иногда видел malloc, завернутый в функцию, которая просто пытается снова, пока память не будет доступна...

void *my_malloc(size_t size)
{
    void *result = NULL;
    while (result == NULL)
        result = malloc(size);
    return result;
}

Но это кажется глупым и, может быть, опасным.)

Какой правильный способ справиться с этим?

4b9b3361

Ответ 1

Если распределение прерывается таким образом, чтобы предотвратить прогресс вперед, единственным приемлемым решением для библиотечного кода является резервное копирование любых распределений и других изменений, которые уже были сделаны в частично завершенной операции, и вернуть код отказа вызывающему. Только вызывающее приложение может знать, что нужно делать правильно. Некоторые примеры:

  • Музыкальный проигрыватель может просто прервать или вернуться в начальное/остановленное состояние и снова ждать ввода пользователя.
  • Текстовому процессору может потребоваться сохранение аварийного дампа текущего состояния документа в файл восстановления, а затем прервать его.
  • Для сервера базы данных высокого класса может потребоваться отклонить и отменить всю транзакцию и сообщить клиенту.

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

Изменить:. Один из возражений некоторых из "прерванных" лагерей повысит мой ответ, так это то, что в системах с overcommit даже вызовы malloc, которые оказались успешными, могут потерпеть неудачу, когда ядро ​​пытается для создания экземпляра физической памяти для выделенной виртуальной памяти. Это игнорирует тот факт, что любой, кто нуждается в высокой надежности, будет отключен с отключением, а также тот факт, что отказ в распределении по крайней мере на 32-битных системах более вероятен из-за исчерпания виртуального адресного пространства, чем исчерпание физического хранилища.

Ответ 2

Просто верните ошибку, как обычно. Поскольку мы говорим об API, вы не знаете, из какой среды вы вызываетесь, поэтому просто возвращайте NULL или следуйте любой другой процедуре обработки ошибок, которую вы уже используете. Вы не хотите зацикливаться навсегда, поскольку вызывающему абоненту может не понадобиться эта память, и они скорее просто знают, что вы не можете справиться с этим, или, возможно, у вызывающего есть пользовательский интерфейс, к которому они могут отправить сообщение.

Большинство API-интерфейсов будут иметь какое-то возвращаемое значение, которое указывает на ошибку для всех функций, другие API требуют, чтобы вызывающий вызывал специальную функцию "check_error", чтобы определить, есть ли ошибка. Вы также можете захотеть, чтобы функция get_error возвращала строку ошибки, которую вызывающий может опционально отображать пользователю или включать в журнал. Он должен быть описательным: "так-то API обнаружил ошибку в функции: не удалось выделить память". Или что угодно. Достаточно того, что, когда кто-то получает сообщение об ошибке, они знают, какой компонент его бросил, и когда он отправляет вам сообщение с сообщением журнала, вы точно знаете, что пошло не так.

Конечно, вы также можете просто сбой, но это не позволяет вызывающему абоненту закрыть что-нибудь еще, что они могут делать, и выглядит уродливым, если ваш код имеет привычку умирать, когда он вызывается, а не возвращает ошибку, люди собираюсь искать библиотеку, которая не активно пытается убить свою программу.

Ответ 3

Стандарт API BLAS для вызовов функций линейной алгебры использует несколько иной подход от предложений "вернуть код ошибки", приведенных здесь: он вызывает определенную документальную функцию и затем возвращает.

Теперь библиотека также предоставляет реализацию этой документированной функции, которая печатает полезное сообщение об ошибке и (по возможности) трассировку стека, а затем прерывается. Это один из способов обработки вещей, а это означает, что случайный пользователь не столкнется с причудливыми проблемами, потому что они забывают проверить код ошибки.

Однако критической точкой этого является определенная документированная функция, так как это означает, что пользователь может также выбрать собственную реализацию этой функции, которая переопределит значение по умолчанию. И эта реализация может сделать много вещей - она ​​может установить глобальный код ошибки, который затем проверяет пользователь, или он может сделать что-то, что пытается очистить память и продолжить.

Такой вид тяжеловесного решения, как для реализации, так и для пользователя, но в тех случаях, когда очевидный код ошибки не подходит, он обеспечивает большую гибкость.

Изменить, чтобы добавить несколько деталей: функция BLAS xerbla (или cblas_xerbla, в интерфейсе C) с ожиданием того, что переопределить его во время соединения - предположение является статическим связыванием. Также есть некоторые важные замечания о том, как это должно быть настроено для динамических библиотек в этом заголовке из Apple (см. Комментарии к SetBLASParamErrorProc, внизу файла) - в случае динамической привязки обратный вызов должен быть зарегистрирован во время выполнения.

См. также полезные заметки от "R." в комментариях ниже о том, как это переопределение, к сожалению, является глобальным, что может вызвать проблемы, если пользователь использует вашу библиотеку как напрямую, так и косвенно через вторую библиотеку, и как пользователь, так и вторая библиотека хотят переопределить обработчик.

Ответ 4

Для библиотеки у вас есть два варианта. Без сотрудничества с приложениями почти все, что вы можете сделать, это передать ошибку обратно в приложение.

С сотрудничеством с приложениями вы можете сделать гораздо больше. Например, вы можете предложить приложению зарегистрировать обратный вызов, который вызывает ваша библиотека, когда malloc возвращает NULL. Вы можете передать обратный вызов количество необходимых вам байтов и то, насколько они вам срочно нужны. (Например, "полностью не сработает", "придется прервать операцию" и т.д.). Тогда автор приложения может решить, следует ли предоставить вам память или нет.

На уровне приложения вы можете сделать гораздо больше. Например, вы можете malloc собрать кучу блоков памяти для использования в качестве "аварийного пула". Если malloc терпит неудачу, вы можете освободить блоки из пула и начать сброс нагрузки, уменьшение кеша или любые другие варианты, которые вы должны уменьшить потребление памяти.

Но вы, как правило, не можете многое сделать в библиотеке, кроме как сообщить сотрудничающее приложение.

Ответ 5

На языке, таком как Java или С#, однозначный ответ обычно "выдает исключение!".

В C один общий подход состоит в том, чтобы обрабатывать ошибку синхронно (например, с помощью кода результата и/или значения флага типа "null" ).

Можно также генерировать асинхронный сигнал (как "исключение Java"... или тяжелый "abort()" ). При таком подходе вы также можете установить пользовательский "обработчик ошибок".

Вот пример использования setjmp/longjmp:

И вот несколько интересных идей:

И здесь хорошая дискуссия о плюсах и минусах использования обратных вызовов C для обработки ошибок:

Ответ 6

Вы можете написать программное обеспечение, для которого не требуется malloc, в первую очередь - безопасный критический материал.

Вы просто обеспечиваете в начале выполнения, который он выделяет, определяет, что ему нужно, и убедитесь, что алгоритмы не будут превышать эти барьеры.

Жестко, но не невозможно.

Ответ 7

Сложно разрабатывать программное обеспечение для чистоты проблем с памятью и продолжать работу. Немногие реальные приложения делают серьезную попытку сделать это. Как автор библиотеки, единственная разумная вещь - сообщить об ошибке вызывающему. (вернуть отказ, выставить исключение, в зависимости от языка и т.д.)

Вы определенно не хотите зацикливать и блокировать общую библиотеку. @R имеет хорошую точку, если возникает сбой, попытайтесь восстановить состояние в исходное состояние.

Обработка проблем с памятью и из-за дискового пространства, вероятно, требует координации во всех частях приложения. Возможно, вы захотите иметь предварительно распределенную память. Вероятно, вы будете цитировать/повторять mallocs, как вы предполагали, но с некоторыми задержками между таймаутом. Это действительно выходит за рамки типичной библиотеки.