Существует метод foo
, который иногда возвращает следующую ошибку:
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
Abort
Есть ли способ использовать блок try
-catch
, чтобы остановить эту ошибку от завершения моей программы (все, что я хочу сделать, это вернуть -1
)?
Если да, то каков его синтаксис?
Как еще я могу справиться с bad_alloc
в C++?
В общем случае вы не можете и не должны пытаться реагировать на эту ошибку. bad_alloc
указывает на то, что ресурс не может быть выделен, потому что недостаточно памяти. В большинстве сценариев ваша программа не может надеяться справиться с этим, и единственным разумным поведением будет ее скорое завершение.
Хуже того, современные операционные системы часто перераспределяют ресурсы: в таких системах malloc
и new
могут создать правильный указатель, даже если свободной памяти недостаточно - std::bad_alloc
никогда не будет выброшен, или, по крайней мере, не является надежным признаком исчерпания памяти. Вместо этого, попытки доступа к выделенной памяти приведут к ошибке сегментации, которую невозможно поймать (вы можете обработать сигнал ошибки сегментации, но после этого вы не сможете возобновить работу программы).
Единственное, что вы можете сделать, поймав std::bad_alloc
, это, возможно, записать ошибку в лог и попытаться обеспечить безопасное завершение программы, освободив оставшиеся ресурсы (но это делается автоматически в ходе обычного разворачивания стека после возникновения ошибки, если программа использует RAII должным образом).
В некоторых случаях программа может попытаться освободить часть памяти и повторить попытку, или использовать вторичную память (= диск) вместо оперативной памяти, но эти возможности существуют только в очень специфических сценариях с жесткими условиями:
Приложения крайне редко контролируют пункт 1 - приложения пользовательского пространства никогда этого не делают, это общесистемная настройка, для изменения которой требуются права root.1
Хорошо, предположим, что вы исправили пункт 1. Теперь вы можете, например, использовать LRU кэш для некоторых ваших данных (возможно, некоторых особенно больших бизнес-объектов, которые могут быть регенерированы или перезагружены по требованию). Далее, вам нужно поместить фактическую логику, которая может потерпеть неудачу, в функцию, которая поддерживает повторную попытку - другими словами, если она будет прервана, вы можете просто запустить ее заново:
lru_cache<widget> widget_cache;
double perform_operation(int widget_id) {
std::optional<widget> maybe_widget = widget_cache.find_by_id(widget_id);
if (not maybe_widget) {
maybe_widget = widget_cache.store(widget_id, load_widget_from_disk(widget_id));
}
return maybe_widget->frobnicate();
}
...
for (int num_attempts = 0; num_attempts < MAX_NUM_ATTEMPTS; ++num_attempts) {
try {
return perform_operation(widget_id);
} catch (std::bad_alloc const&) {
if (widget_cache.empty()) throw; // ошибка памяти в другом месте.
widget_cache.remove_oldest();
}
}
// Здесь обрабатываем слишком много неудачных попыток.
Но даже здесь использование std::set_new_handler
вместо обработки std::bad_alloc
дает ту же пользу и будет намного проще.
1 Если вы создаете приложение, которое делает контроль пункта 1, и вы читаете этот ответ, пожалуйста, напишите мне, мне искренне интересно узнать о ваших обстоятельствах.
Обычно считается, что если оператор new
не могу выделить динамической памяти требуемого размера, он должен сгенерировать исключение типа СТД::bad_alloc
.
Однако происходит нечто большее, еще до bad_alloc
исключение:
Раздел C++3.7.4.1.3 03: говорит
функция выделения, которые не удается выделить память может ссылаться на установленные new_handler(18.4.2.2), если таковые имеются. [Примечание: программы-предоставленная функция распределения может получить адрес текущей установленной с помощью функции new_handler set_new_handler (18.4.2.3).] Если функция распределения объявляется с пустым исключение-спецификация (15.4), бросать(), не удается выделить память, он возвращает указатель null. Любые другие функции выделения, которые не удается выделить память только указать на ошибку, выкидывать-ное исключение из класс std::bad_alloc (18.4.2.1) или класс, производный от std::bad_alloc.
Рассмотрим следующий пример кода:
#include <iostream>
#include <cstdlib>
// function to call if operator new can't allocate enough memory or error arises
void outOfMemHandler()
{
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
int main()
{
//set the new_handler
std::set_new_handler(outOfMemHandler);
//Request huge memory size, that will cause ::operator new to fail
int *pBigDataArray = new int[100000000L];
return 0;
}
В приведенном выше примере оператор new (скорее всего) будет не в состоянии выделить место для 100,000,000 чисел, и outOfMemHandler функции
()` будет называться, и программа прервется после выдачи сообщения об ошибке.]2
Как видно здесь по умолчанию оператор new
когда можете выполнить просьбу, памяти, вызывать повторно функцию Нью-обработчик
, пока он не может найти достаточно памяти или нет больше новых обработчиков. В приведенном выше примере, если мы называем СТД::метод abort()
, outOfMemHandler()
будет неоднократно. Поэтому обработчик должен либо гарантировать, что в следующий распределение завершается успешно, или зарегистрируйтесь другой обработчик, или зарегистрировать обработчик, или не возвращать (т. е. завершить программу). Если нет нового обработчика и выделение не удается, оператор будет бросать исключение.
new_handler
и set_new_handler
?new_handler-это typedef для указателя на функцию, которая принимает и ничего не возвращает, и set_new_handler-это функция, которая принимает и возвращает
new_handler`.
Что-то вроде:
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
set_new_handler's параметр является указателем на функцию operator новый
должен позвонить, если он может'т выделить запрошенную память. Ее возвращаемым значением является указатель на ранее зарегистрированный обработчик, или null если не было никакого предыдущего обработчика.
Учитывая поведение `новую ' хорошо разработана пользовательская программа должна обрабатывать из памяти, обеспечивая правильное new_handler м'который выполняет одно из следующих действий:
*Освободить память: это может привести к следующей попытке выделения памяти оператор new'с петлей для достижения успеха. Один из способов реализации этого является выделение большого блока памяти при запуске программы, а затем выпустить его для использования в программе впервые новый-обработчика.
Установить другой новый-обработчик: если тока новый-обработчик может'т делать какие-либо больше памяти, и есть еще один новый-обработчик, который может, то тока новый-обработчик может установить другой новый-обработчик на своем месте (по телефону set_new_handler
). Следующий оператор время новые вызовы, новые функции-обработчика, он будет получать наиболее недавно установили.
(Вариация на эту тему для нового обработчика, чтобы изменить свое поведение, чтобы в следующий раз она'ы ссылаться, это нечто другое. Одним из способов достижения этого является, чтобы иметь Нью-обработчик изменения статического, специфичные для области имен или глобальных данных, что влияет на новую-обработчик'ы поведения.)
Удалить новый-обработчик: это делается путем передачи нулевого указателя на set_new_handler
. Без новых-обработчик установлен, оператор new
генерирует исключение ((конвертируемые) с std::bad_alloc
), когда выделение памяти завершается неудачей.
Исключение кабриолет с std::bad_alloc
. Такие исключения не быть пойманным оператор new
, но будет распространяться на сайт из запроса к памяти.
Не вернуть: по телефону "отмена" или "выход".
Вы можете поймать его, как любое другое исключение:
try {
foo();
}
catch (const std::bad_alloc&) {
return -1;
}
Что именно вы можете полезного сделать с этого момента, зависит от вас, но технически это определенно осуществимо.
Я бы не советовал этого делать, поскольку bad_alloc
означает, что у вас кончилась память. Лучше просто сдаться, а не пытаться восстановить память. Однако вот решение, о котором вы спрашиваете:
try {
foo();
} catch ( const std::bad_alloc& e ) {
return -1;
}
Я могу предложить более простой (и даже быстрее) решение для этого. оператор new
будет возвращать null, если не удалось выделить память.
int fv() {
T* p = new (std::nothrow) T[1000000];
if (!p) return -1;
do_something(p);
delete p;
return 0;
}
Я надеюсь, что это может помочь!
Пусть ваш ФОО программы Выход контролируемым образом:
#include <stdlib.h> /* exit, EXIT_FAILURE */
try {
foo();
} catch (const std::bad_alloc&) {
exit(EXIT_FAILURE);
}
Затем написать программная оболочка, что вызывает реальную программу. Поскольку адресные пространства разделены, состояние вашего программной оболочке всегда хорошо выражен.