Может ли оператор C ++ `new` когда-либо генерировать исключение в реальной жизни? - PullRequest
43 голосов
/ 23 марта 2010

Может ли оператор new вызвать исключение в реальной жизни?

И если да, есть ли у меня какие-либо варианты обработки такого исключения, кроме как убить мое приложение?

Обновление:

Проверяют ли какие-либо приложения, работающие в реальных условиях, new с высокой нагрузкой, на наличие сбоев и восстанавливаются ли при отсутствии памяти?


Смотри также:

Ответы [ 18 ]

39 голосов
/ 23 марта 2010

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

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

24 голосов
/ 23 марта 2010

Операторы new и new [] должны выдавать std::bad_alloc, но это не всегда так, поскольку поведение иногда может быть изменено.

Можно использовать std::set_new_handler, и вдруг может произойти что-то совершенно иное, чем бросок std::bad_alloc. Хотя стандарт требует, чтобы пользователь либо выделил память, либо прервал ее, или выбросил std::bad_alloc. Но, конечно, это может быть не так.

Отказ от ответственности: я не предлагаю делать это.

18 голосов
/ 24 марта 2010

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

Если вы запускаете свою программу на компьютере с меньшим объемом физической памяти, чем максимальный размер виртуальной памяти (2 ГБ в стандартной Windows), вы обнаружите, что, как только вы выделите объем памяти, приблизительно равный доступной физической памяти, дальнейшее распределение будет выполнено успешно, но вызовет подкачку на диск. Это замедлит вашу программу, и вы, возможно, не сможете добраться до точки исчерпания виртуальной памяти. Таким образом, вы можете не получить исключение.

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

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

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

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

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

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

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

9 голосов
/ 23 марта 2010

В системах Unix принято запускать длительные процессы с ограничением памяти (используя ulimit), чтобы он не занимал всю системную память. Если ваша программа достигнет этого предела, вы получите std::bad_alloc.


Обновление для редактирования OP: наиболее типичным случаем восстановления программ из состояния нехватки памяти является сборка мусора, которая затем выполняет GC и продолжает работу. Тем не менее, этот вид GC по требованию действительно только для последних усилий; Обычно хорошие программы стараются периодически собирать GC, чтобы уменьшить нагрузку на коллектора.

Для программ, не относящихся к GC, не так часто восстанавливаться после проблем с нехваткой памяти, но для серверов, подключенных к Интернету, один из способов восстановления - просто отклонить запрос, из-за которого память заканчивается «временным» ошибка. (Стратегия «первым пришел, первым обслужен».)

7 голосов
/ 23 марта 2010

Вам не нужно обрабатывать исключение в каждом new :) Исключения могут распространяться. Разработайте свой код так, чтобы в каждом «модуле» были определенные точки, где эта ошибка обрабатывается.

7 голосов
/ 23 марта 2010

Это зависит от компилятора / среды выполнения и от operator new, который вы используете (например, некоторые версии Visual Studio не будут выбрасываться из поля , а скорее вернут указатель NULL а-ля malloc вместо.)

Вы всегда можете catch std::bad_alloc исключение или явно использовать nothrow new для возврата NULL вместо броска. (Также см. мимо сообщений StackOverflow , вращающихся вокруг темы.)

Обратите внимание, что operator new, как и malloc, , будет давать сбой, когда у вас закончится память, не хватает адресного пространства (например, 2-3 ГБ в 32-разрядном процессе в зависимости от ОС) вне квоты (ulimit уже упоминалось) или из смежного адресного пространства (например, фрагментированная куча)

7 голосов
/ 23 марта 2010

osgx сказал:

Есть ли в реальных приложениях проверяет много новостей и может восстановить, когда нет памяти?

Я уже ответил на это ранее в моем ответе на на этот вопрос , который приводится ниже:

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

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

4 голосов
/ 23 марта 2010

Да, new может бросить std::bad_alloc (подкласс std::exception), который вы можете поймать.

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

T* p = new (nothrow) T(...);
if (p == 0)
{
    // Do something about the bad allocation!
}
else
{
    // Here you may use p.
}
4 голосов
/ 23 марта 2010

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

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

3 голосов
/ 23 марта 2010

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

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

...