Как бороться с bad_alloc в C ++? - PullRequest
47 голосов
/ 27 февраля 2012

Существует метод с именем foo, который иногда возвращает следующую ошибку:

terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Abort

Есть ли способ, которым я могу использовать блок try - catch, чтобы эта ошибка не прервала мою программу (все, что я хочу сделать, это вернуть -1)?

Если да, то каков его синтаксис?

Как еще можно разобраться с bad_alloc в C ++?

Ответы [ 6 ]

79 голосов
/ 27 февраля 2012

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

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

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

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

37 голосов
/ 27 февраля 2012

Что такое стандартное поведение C ++ для new в c ++?

Обычно считается, что если оператор new не может выделить динамическую память запрошенного размера, он должен вызвать исключение типаstd::bad_alloc.
Однако что-то еще происходит еще до того, как выдается исключение bad_alloc:

C ++ 03 Раздел 3.7.4.1.3: говорит

Функция выделения, которая не выделяет хранилище, может вызывать установленный в данный момент new_handler (18.4.2.2), если таковой имеется.[Примечание: предоставляемая программой функция выделения может получить адрес установленного в данный момент new_handler, используя функцию set_new_handler (18.4.2.3).] Если функция выделения объявлена ​​с пустой спецификацией исключений (15.4), throw () завершается неудачновыделить память, он должен вернуть нулевой указатель.Любая другая функция выделения, которая не может выделить хранилище, должна указывать на сбой только путем генерирования исключения класса 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;
}

В приведенном выше примере operator new (наиболее вероятно) не сможет выделить место для 100 000 000 целых чисел, и будет вызвана функция outOfMemHandler(), и программапрерывание после выдачи сообщения об ошибке.

Как видно из приведенного здесь поведения по умолчанию оператора new, когда не удается выполнить запрос памяти, является повторный вызов функции new-handler, пока она не сможетнайти достаточно памяти или больше нет новых обработчиков.В приведенном выше примере, если мы не вызовем std::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 - указатель на функцию, которую должен вызывать оператор new, если он не может выделить запрошенную память.Его возвращаемое значение - указатель на ранее зарегистрированную функцию-обработчик, или ноль, если не было предыдущего обработчика.

Как обрабатывать нехватку памяти в C ++?

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

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

Установка другого нового-handler: Если текущий новый обработчик не может сделать больше доступной памяти, и если есть другой новый обработчик, который может, то текущий новый обработчик может установить другой новый обработчик на свое место (с помощьюзвонит set_new_handler).В следующий раз, когда оператор new вызывает функцию new-handler, он получит последнюю установленную функцию.

(Разновидностью этой темы является то, что new-handler изменяет свое поведение, поэтому в следующий развызывается что-то другое. Один из способов добиться этого - заставить новый обработчик изменять статические, специфичные для пространства имен или глобальные данные, которые влияют на поведение нового обработчика.)

Удалите новый-handler: Это делается путем передачи нулевого указателя на set_new_handler.Если не установлен новый обработчик, operator new будет выдавать исключение ((преобразуемое в) std::bad_alloc), если выделение памяти окажется неудачным.

Бросить исключение , преобразуемое в std::bad_alloc.Такие исключения не будут перехвачены operator new, но будут распространяться на сайт, инициирующий запрос памяти.

Не возвращается: Позвонив по номеру abort или exit.

31 голосов
/ 27 февраля 2012

Вы можете поймать его, как и любое другое исключение:

try {
  foo();
}
catch (const std::bad_alloc&) {
  return -1;
}

То, что вы можете сделать с этой точки зрения, зависит от вас, но технически это определенно выполнимо.

8 голосов
/ 27 февраля 2012

Я бы не советовал, поскольку bad_alloc означает, что у вас недостаточно памяти . Лучше всего просто сдаться, чем пытаться восстановиться. Однако вот решение, которое вы просите:

try {
    foo();
} catch ( const std::bad_alloc& e ) {
    return -1;
}
5 голосов
/ 12 июня 2014

Я могу предложить более простое (и даже более быстрое) решение для этого.Оператор new вернет ноль, если память не может быть выделена.

int fv() {
    T* p = new (std::nothrow) T[1000000];
    if (!p) return -1;
    do_something(p);
    delete p;
    return 0;
}

Надеюсь, это может помочь!

1 голос
/ 12 марта 2014

Позвольте вашей foo программе выйти контролируемым образом:

#include <stdlib.h>     /* exit, EXIT_FAILURE */

try {
    foo();
} catch (const std::bad_alloc&) {
    exit(EXIT_FAILURE);
}

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

...