Страницы

Поиск по вопросам

вторник, 24 декабря 2019 г.

Язык C, malloc() и NULL

#c #windows #null #malloc


Хотел бы обсудить, что делать, если malloc() вернул NULL.

Ясное дело, что если ведется разработка библиотеки или какого-либо "кирпичика", который
будет использоваться неизвестно в каких условиях, то вызов abort() совершенно неприемлем. 

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

1) От людей, которые пишут под Linux, слышал, что они не проверяют результат вызова
malloc(), обосновывая это тем, что malloc() запросто может вернуть не NULL, а при попытке
обращения к памяти через такой указатель все рухнет. Ибо память изначально выделяется
в виртуальной области, и такая память не связывается с реальной до тех пор, пока к
ней не произойдет обращение.

В Windows 10/8/7/Em/CE тоже так?

2) Если память выделяется маленькими порциями, то может произойти такое, что при
неудачном malloc() мы хотим сделать printf(), не говоря уже про fopen() + fwrite(),
а памяти нет, и все совсем падает. Это реально?

3) Довольно долго думал над тем, как защитить программу, которая крутится на ПЛК,
обеспечивая передачу телеметрии. Сам объект находится далеко - "три для ехать, а потом
еще на вертолете с пересадками". В общем, классический пример промышленных систем.

Приложение многопоточное. Одни потоки отправляют/получают данные, другие устанавливают
соединение по TCP, третьи - ведут файлы журналов. Много библиотек, часто - закрытых в dll.

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

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

4) Я так понимаю, на исчерпывающем большинстве промышленных устройств сторожевой
таймер предусмотрен как раз из-за того, что существуют вот такие неразрешимые программным
путем проблемы, как неудачный malloc() и исчерпание памяти?
    


Ответы

Ответ 1



malloc всегда должет возвращать адрес, если malloc вернул null тогда это аварийная ситуация. Вы можете 1) Прекратить работу всей программы. 2) Вы можете прекратить работу "процесса" (не процесса как Process, а класса, библиотеки, функции или набора функций, например процесс компрессии данных), который не может завершить свою работу, освободить память занимаемую этим "процессом". Тут я солидарен с alexolut Надо не забывать что у программы есть детерминированое - определённое поведение, и недетерминированое. Нужно стремится делать детерминированое поведение. В программе желательно до начала процесса (а не вовремя) опеределить сколько нужно на процесс памяти, и не начинать процесс (или работу программы) если его не возможно выполнить. Нужно ограничивать аппетиты пользователя, если вам вместо 100Мб прислали 10Gb, то сообщить что файлы/потоки свыше 2Gb ваша программа не обрабатывает (условно, для примера). Нужно определить заранее на какие обьёмы вы хотите расщитывать, и проверять есть ли они (обьемы) в наличии. Если вы резервируете память столько незнаю сколько - то для небольших программ будет всё ок, а для больших - может закончится нехваткой памяти. Т.е. невозможно сделать malloc столько незнаю cколько. В ответственных за скорость местах лучше использовать пулы памяти. Недостаток пулов - часть "неиспользуемой" памяти просто лежит. Нужно соблюдать несколько правил... и тогда у вас память не закончится неожиданно. резервировать в ответственных местах память заранеее ограничивать глубину рекурсии в рекурсивных функциях (хвостовая рекурсия - отдельный случай) соблюдать порядок malloc и free. Желательно разделять память для долгого использования и для кратковременного. Сколько зарезервировано паралельными процессами. Проверять наличие утечки памяти (когда malloc больше чем free, но для этого нужно знать какое число, разница колличества вызовов malloc/free должна быть, и для этого нужно знать сколько памяти долгого использования зарезервировано). делать пулы, или массивы памяти, для возможности освободить память по завершению "процесса" которую будет использовать конкретный процесс (действие) (упростит освобожнение). память может закончится в двух случаях а) неправильно построеная "модель памяти" (неопределённое поведение программы которое не предусмотрено) б) пользователь использует компьютер с реально маленьким размером оперативки и файлом подкачки. Второй случай редкий, но проверить его на старте стоит. Ещё хочу заметить, что ОС уровня "приложение" оперирует виртуальной памятью, поэтому может такое быть, что ваше приложение занимает меньше памяти чем вы резервировали. Тут Язык C, глобальные массивы часный случай когда реально приложение занимает меньше памяти, чем виртуально. Если для вас важна стабильность - резервируйте при запуске нужное количество памяти - и проблемы с malloc у вас не будет. Ведь главный вопрос не в том хватает памяти или не хватает, а в том - сколько вам нужно.

Ответ 2



Если нужно подготовиться к обработке исчерпания памяти, то первым делом стоит обеспечить (зарезервировать) уже некоторый объём памяти для этих нужд. И всю обработку производить, опираясь на эти, заранее выделенные, объёмы. Когда память закончилась - это значит, что что-то пошло не так, что-то что мы заранее не учли. Ведь в нормальной ситуации устройство должно обычно обеспечивать режим работы 24/7. Поэтому когда память вдруг неожиданно кончилась, самое правильное, что здесь можно сделать, это залогировать проблемную ситуацию и перезапуститься (упомянутый вами сторожевой таймер как раз для этих целей). А вот для логирования как раз будет полезен заранее предопределённый объем память. Работать с ним можно по-разному: либо непосредственно ссылаться на эту память, либо выполнить для неё free и рассчитывать, что последующие malloc, необходимые для корректной обработки аномальной ситуации, отработают корректно, т.е. предоставят недавно освобождённую память.

Ответ 3



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

Комментариев нет:

Отправить комментарий