Подтвердить что ты не робот

Что произойдет, если "throw" не сможет выделить память для объекта исключения?

Из С++ 11 standard (15.1.p4):

Память для объекта исключения выделяется в неуказанном путь, за исключением случаев, указанных в 3.7.4.1.

Что делать, если выделение не удастся - вместо этого оно выбрасывает std::bad_alloc? Вызовите std::terminate? Не выбрано?

4b9b3361

Ответ 1

(предоставляя свой собственный ответ... Я подожду несколько дней, и если с ним не будет никаких проблем, я сделаю это как принято)

Я потратил некоторое время на изучение этого, и вот что я раскопал:

  • Стандарт С++ не указывает, что произойдет в этом случае
  • Clang и GCC, похоже, используют С++ Itanium ABI

Itanimum ABI предлагает использовать кучу для исключений:

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

...

Память будет распределяться с помощью __cxa_allocate_exception обычной библиотеки.

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

Он также упоминает следующее:

Если __cxa_allocate_exception не может выделить объект исключения в этих ограничениях, он вызывает terminate()

Да... в GCC и Clang "throw myX();" может убить ваше приложение, и вы не можете ничего с этим поделать (возможно, может помочь ваш собственный __cxa_allocate_exception), но он, безусловно, не будет переносимым)

Это становится еще лучше:

3.4.1 Выделение объекта исключения

Память для объекта исключения будет выделена __cxa_allocate_exception, с общими требованиями, описанными в разделе 2.4.2. Если нормальное распределение сбой, то он попытается выделить один из аварийных буферов, описанной в разделе 3.3.1, при следующих ограничениях:

  • Размер объекта исключения, включая заголовки, составляет менее 1 КБ.
  • В текущем потоке уже нет четырех буферов.
  • Есть менее 16 других потоков, содержащих буферы, или этот поток будет ждать, пока один из других не выпустит свои буферы, прежде чем приобретать их.

Да, ваша программа может просто висеть! Предоставленные шансы на это небольшие - вам нужно будет исчерпать память, и ваши потоки должны использовать все 16 аварийных буферов и ввести ожидание другого потока, который должен генерировать исключение. Но если вы делаете что-то с std::current_exception (например, исключение цепочки и передача их между потоками) - это не так маловероятно.

Вывод:

Это недостаток в стандарте С++ - вы не можете писать 100% надежные программы (которые используют исключения). Пример текстовой книги - это сервер, который принимает подключения от клиентов и выполняет отправленные задачи. Очевидным подходом к решению проблем было бы исключение, которое будет разматывать все и закрыть соединение - все остальные клиенты не будут затронуты, и сервер будет продолжать работать (даже в условиях низкой памяти). Увы, такого сервера невозможно писать на С++.

Вы можете утверждать, что современные системы (то есть Linux) убьют такой сервер, пока мы не достигнем этой ситуации. Но (1) это не аргумент; (2) диспетчер памяти может быть настроен на overcommit; (3) Убийца OOM не будет запущен для 32-разрядного приложения, работающего на 64-битном аппаратном обеспечении с достаточной памятью (или при искусственном ограничении памяти приложения).

В личной заметке я очень злюсь об этом открытии - многие годы я утверждал, что мой код грациозно обрабатывает из памяти. Оказывается, я лгал своим клиентам.:-( Могло бы также начать перехватывать выделение памяти, вызвать std::terminate и обработать все связанные функции как noexcept - это, безусловно, сделает мою жизнь проще (кодируя). Неудивительно, что они все еще используют Ada для программных ракет.

Ответ 2

[intro.compliance]/2 Хотя в этом международном стандарте указаны только требования к реализациям на С++, эти требования часто легче понять, если они сформулированы как требования к программам, частям программ или исполнению программ. Такие требования имеют следующее значение:

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

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

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

Ответ 3

В текущем ответе уже описывается, что делает GCC. Я проверил поведение MSVC - он выделяет исключение в стеке, поэтому распределение не зависит от кучи. Это делает возможным переполнение стека (объект исключения может быть большим), но обработка не покрывается стандартным С++.

Я использовал эту короткую программу для изучения того, что происходит во время исключения throw:

#include <iostream>

class A {
public:
    A() { std::cout << "A::A() at " << static_cast<void *>(this) << std::endl; }
    A(const A &) { std::cout << "A::A(const A &) at " << static_cast<void *>(this) << std::endl; }
    A(A &&) { std::cout << "A::A(A &&) at " << static_cast<void *>(this) << std::endl; }
    ~A() { std::cout << "A::~A() at " << static_cast<void *>(this) << std::endl; }
    A &operator=(const A &) = delete;
    A &operator=(A &&) = delete;
};

int main()
{
    try {
        try {
            try {
                A a;
                throw a;
            } catch (const A &ex) {
                throw;
            }
        } catch (const A &ex) {
            throw;
        }
    } catch (const A &ex) {
    }
}

Когда сборка с выходом GCC ясно показывает, что выделенное исключение выделяется далеко от стека:

A::A() at 0x22cad7
A::A(A &&) at 0x600020510
A::~A() at 0x22cad7
A::~A() at 0x600020510

Когда сборка с выходом MSVC показывает, что исключение выделяется рядом с стеком:

A::A() at 000000000018F4E4
A::A(A &&) at 000000000018F624
A::~A() at 000000000018F4E4
A::~A() at 000000000018F624

Дополнительный экзамен с отладчиком показывает, что обработчики и деструкторы обработчиков выполняются в верхней части стека, поэтому потребление стека растет с каждым блоком catch, начиная с первого броска, и до тех пор, пока std::uncaught_exceptions() не станет 0.

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

Чтобы доказать то же самое с GCC, вам кажется, что вам нужно будет доказать там не более четырех вложенных исключений, а исключения имеют размер меньше 1KiB (сюда входит заголовок). Кроме того, если в каком-то потоке имеется более четырех вложенных исключений, вам также необходимо доказать, что не существует тупика, вызванного распределением аварийного буфера.

Ответ 4

Фактически указано, что если сбой для объекта исключения не удался, bad_alloc должен быть выброшен, а реализация также может вызвать новый обработчик.

Это то, что актуально указано в стандартном разделе С++ (§3.7.4.1), на вашем сайте [basic.stc.dynamic.allocation]:

Функция распределения, которая не может выделить хранилище, может вызвать текущую установленную функцию нового обработчика (21.6.3.3), если таковые имеются. [ Заметка: Функция распределения по программе может получить адрес текущего установлен new_handler используя станд:: get_new_handler функции (21.6.3.4). - конечная нота ] Если распределение функция, которая имеет исключающую исключение спецификацию исключений (18.4), не может выделить хранилище, она должна вернуть значение null указатель. Любая другая функция распределения, которая не может выделить хранилище, должна указывать на отказ только путем исключение (18.1) типа, которое будет соответствовать обработчику (18.3) типа станд:: bad_alloc (21.6.3.1).

Затем это напомнили в [except.terminate]

В некоторых ситуациях обработка исключений должна быть отменена для менее тонких методов обработки ошибок. [ Заметка: Эти ситуации: - (1.1) когда механизм обработки исключений, после завершения инициализации объекта исключения, но перед активацией обработчика исключения (18.1) *

Таким образом, itanium ABI не соответствует стандартной спецификации С++, поскольку он может блокировать или вызывать terminate, если программе не удается выделить память для объекта исключения.