Когда я что-то «кидаю», где это сохраняется в памяти?

82

Я понимаю, что когда что-то thrown, стек `` разматывается '' до точки, где он был пойман, и запускаются деструкторы экземпляров класса в стеке в каждом контексте функции (вот почему вы не должны генерировать исключение из деструктора - вы могли бы бросить второй) ... но мне интересно, где в памяти хранится брошенный мною объект, когда это происходит?

Это зависит от реализации? Если да, то есть ли какой-то конкретный метод, используемый наиболее популярными компиляторами?

sje397
источник
1
Хороший вопрос - вероятно, это не предусмотрено стандартом, поскольку в стандарте даже не говорится, что у вас должен быть стек. Практически, возможно, он размещен в куче и освобождается при выходе из блока catch?
Kerrek SB 07
@Kerrek: Я думаю, что более вероятно, что объект помещается в стопку «внизу». Затем развернитесь вверх, вспомнив, где оно было, и как только вы закончите раскручивание (включая предоставление любым предложениям catch возможность обратиться к исключению по ссылке и повторно raiseего), уничтожьте его. Проблема с распределением кучи заключается в следующем: что делать, если попытка выделить исключение не удалась? Вам придется прервать выполнение (так же, как и при переполнении стека, если поместить его в стек "не удается" из-за нехватки места). В отличие от Java, реализация не может вместо этого генерировать другое исключение.
Стив Джессоп,
@Steve: Также возможно - в статье Кирилла говорится, что исключение размещается в стеке, а вспомогательная информационная структура записывает его адрес, средство удаления и т. Д., Но я полагаю, что реализации могут делать это любым способом?
Kerrek SB 07
@Kerrek: да, при условии, что он должен фактически генерировать исключение, и обычная проблема, связанная с исчерпанием стека, позволяет реализации уклоняться от таких обязанностей :-) Если вы поместите его в стек, то из-за исключения по статическому типу выражения броска, ваш стек рамки для всей функции может включать в себя любое пространство , которое требуется , чтобы бросить этот тип, хотя я не знаю , если MSVC делает это.
Стив Джессоп,

Ответы:

51

Да, ответ зависит от компилятора.

Быстрый эксперимент с моим compiler ( g++ 4.4.3) показывает, что его библиотека времени выполнения сначала пытается сохранить mallocпамять для исключения и, в случае неудачи, пытается выделить место в «аварийном буфере» всего процесса, который находится в сегменте данных. Если не получается, звонит std::terminate().

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

Соответствующая функция __cxa_allocate_exception:

extern "C" void *
__cxxabiv1::__cxa_allocate_exception(std::size_t thrown_size) throw()
{
  void *ret;

  thrown_size += sizeof (__cxa_refcounted_exception);
  ret = malloc (thrown_size);

  if (! ret)
    {
      __gnu_cxx::__scoped_lock sentry(emergency_mutex);

      bitmask_type used = emergency_used;
      unsigned int which = 0;

      if (thrown_size > EMERGENCY_OBJ_SIZE)
        goto failed;
      while (used & 1)
        {
          used >>= 1;
          if (++which >= EMERGENCY_OBJ_COUNT)
            goto failed;
        }

      emergency_used |= (bitmask_type)1 << which;
      ret = &emergency_buffer[which][0];

    failed:;

      if (!ret)
        std::terminate ();
    }

  // We have an uncaught exception as soon as we allocate memory.  This
  // yields uncaught_exception() true during the copy-constructor that
  // initializes the exception object.  See Issue 475.
  __cxa_eh_globals *globals = __cxa_get_globals ();
  globals->uncaughtExceptions += 1;

  memset (ret, 0, sizeof (__cxa_refcounted_exception));

  return (void *)((char *)ret + sizeof (__cxa_refcounted_exception));
}

Не знаю, насколько типична эта схема.

NPE
источник
"в этом случае mallocвызов завершится неудачно" - обычно, но не обязательно.
Ayxan Haqverdili
20

С этой страницы :

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

Теперь это только Itanium ABI, и я ищу подробности, относящиеся к GCC, Clang и MSVC. Однако стандарт ничего не определяет, и это кажется очевидным способом реализации хранилища исключений, так что ...

Гонки легкости на орбите
источник
1
Все еще ищете подробности? Иногда было бы неплохо принять более одного ответа :)
sje397 08
4

Я не знаю, ответит ли это на ваш вопрос, но это (Как компилятор C ++ реализует обработку исключений) отличная статья об обработке исключений вообще:. Очень рекомендую (:

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

Кирилл Киров
источник
Найдите на excpt_infoэтой странице информацию о том, как это делает MSVC.
Стив Джессоп,
1
Эта ссылка действительно описывает, как этого не делать в хорошей реализации. Я знаю, что некоторые старые VC ++ использовали что-то подобное, но я сомневаюсь, что вы найдете это в любом современном компиляторе.
Джеймс Канце
0

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

Калеб
источник
-2

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

Джеймс Канце
источник
3
Ваш ответ противоречит сам себе.
Гонки легкости на орбите
Как? Он представляет ограничения для реализации, которые неявно указаны в стандарте.
Джеймс Канце
Один пример: «это не может быть в куче» ... «Реализация может использовать кучу большую часть времени». Кроме того, вся первая половина ответа неверна, как объясняется в других ответах (и, действительно, частично во второй части вашего!).
Гонки за легкостью на орбите
Стандарт требует std::bad_allocдля работы. Это означает, что реализация не может систематически использовать кучу. Стандарт этого не позволяет. Я основываю свои претензии на стандарте.
Джеймс Канце
ФСВО «систематически», это правильно. Однако, как вы утверждаете во второй части своего ответа, реализация действительно может использовать кучу ^ H ^ H ^ H ^ Hfreestore в сочетании с альтернативами.
Гонки за легкостью на орбите