Как справиться с bad_alloc в C++?

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

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

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

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

Как еще я могу справиться с bad_alloc в C++?

Комментарии к вопросу (4)

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

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

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

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

  1. [Приложение должно гарантировать, что оно работает в системе, которая не перераспределяет память] (https://serverfault.com/a/606193/1189), т.е. оно сигнализирует о сбое при выделении памяти, а не позже.
  2. Приложение должно иметь возможность освободить память мгновенно, без каких-либо дальнейших случайных выделений за это время.

Приложения крайне редко контролируют пункт 1 -  приложения пользовательского пространства никогда этого не делают, это общесистемная настройка, для изменения которой требуются права root.1

Хорошо, предположим, что вы исправили пункт 1. Теперь вы можете, например, использовать LRU кэш для некоторых ваших данных (возможно, некоторых особенно больших бизнес-объектов, которые могут быть регенерированы или перезагружены по требованию). Далее, вам нужно поместить фактическую логику, которая может потерпеть неудачу, в функцию, которая поддерживает повторную попытку -  другими словами, если она будет прервана, вы можете просто запустить ее заново:

lru_cache widget_cache;

double perform_operation(int widget_id) {
    std::optional 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, и вы читаете этот ответ, пожалуйста, напишите мне, мне искренне интересно узнать о ваших обстоятельствах.

Комментарии (26)

Что стандарт C++ указанное поведение "новой" в C++?

Обычно считается, что если оператор 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 
#include 

// function to call if operator new can't allocate enough memory or error arises
void outOfMemHandler()
{
    std::cerr 
Комментарии (2)
Решение

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

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

Что именно вы можете полезного сделать с этого момента, зависит от вас, но технически это определенно осуществимо.

Комментарии (0)

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

try {
    foo();
} catch ( const std::bad_alloc& e ) {
    return -1;
}
Комментарии (5)

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

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

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

Комментарии (0)

Пусть ваш ФОО программы Выход контролируемым образом:

#include      /* exit, EXIT_FAILURE */

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

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

Комментарии (0)