C API дизайн: что делать, когда malloc возвращает NULL? - PullRequest
20 голосов
/ 29 января 2012

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

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

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

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

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

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

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

Ответы [ 7 ]

13 голосов
/ 29 января 2012

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

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

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

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

4 голосов
/ 29 января 2012

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

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

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

3 голосов
/ 29 января 2012

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

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

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

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

3 голосов
/ 29 января 2012

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

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

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

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

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

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

2 голосов
/ 29 января 2012

Вы можете написать программное обеспечение, которое не требует malloc, во-первых - безопасные критические вещи.

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

Трудно, но не невозможно.

2 голосов
/ 29 января 2012

В таких языках, как Java или C #, однозначным ответом обычно является «выбросить исключение!».

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

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

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

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

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

1 голос
/ 29 января 2012

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

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

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

...