Коли я щось `кидаю ', де воно зберігається в пам'яті?


82

Я розумію, що коли щось має значення thrown, стек `` розмотується '' до точки, де воно потрапляє, і запускаються деструктори екземплярів класів у стеці в кожному контексті функції (саме тому не слід кидати виняток з деструктора - Ви могли б в кінці кинути другий) ... але мені цікаво, де в пам'яті зберігається об'єкт, який я кинув, поки це відбувається?

Чи залежить це від реалізації? Якщо так, чи існує певний метод, який використовується більшістю популярних компіляторів?


1
Гарне запитання - це, мабуть, не визначено стандартом, оскільки стандарт навіть не говорить про те, що ви повинні мати стек. Практично, можливо, він виділяється на купу і звільняється при виході блоку catch?
Kerrek SB

@Kerrek: Я думаю, що, швидше за все, об'єкт розміщується в стеку вниз "внизу". Потім розкрутіть вгору, пам’ятаючи, де це було, і після закінчення розмотування (включаючи надання будь-яким клаузулам можливості усунення винятку за допомогою посилання та відновлення raise), знищіть його. Проблема з розподілом купи полягає в тому, що ви робите, якщо ваша спроба розподілити виняток не вдається? Вам доведеться перервати (так само, як і для переповнення стека, якщо розміщення його в стеці "не вдається" через брак місця). На відміну від Java, реалізації не дозволяється створювати інший виняток.
Steve Jessop

@Steve: Також можливо - у статті Кирила сказано, що виняток виділено в стеці, а допоміжна інформаційна структура записує його адресу та видалювач тощо, але я думаю, що реалізації можуть робити це як завгодно?
Kerrek SB

@Kerrek: так, з урахуванням обмеження, що воно має фактично викинути виняток, і звичайна проблема, через яку закінчується стек, дозволяє реалізації ухилятися від таких обов'язків :-) Якщо ви ставите його в стек, то тому, що викиди викидаються за статичним типом виразу throw, ваш кадр стека для всієї функції може містити будь-який простір, необхідний для викидання цього типу, хоча я не знаю, чи робить це MSVC.
Steve Jessop

Відповіді:


51

Так, відповідь залежить від компілятора.

Швидкий експеримент з моїм компілятором ( 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));
}

Я не знаю, наскільки типова ця схема.


"у такому випадку mallocдзвінок не вдасться" добре, як правило, але не обов'язково.
Ayxan Haqverdili

20

З цієї сторінки :

Зберігання потрібне для виключення. Цей накопичувач повинен зберігатися під час розмотування стека, оскільки він буде використовуватися обробником і повинен бути безпечним для потоків. Тому зберігання об'єктів винятків, як правило, буде виділено в купі , хоча реалізації можуть забезпечити аварійний буфер для підтримки викидів помилок bad_alloc за умов низької пам'яті.

Зараз це лише Itanium ABI, і я шукаю деталі, характерні для GCC, Clang та MSVC. Однак стандарт нічого не вказує, і це, мабуть, очевидний спосіб реалізації сховища винятків, тому ...


1
Все ще шукаєте деталі? Іноді було б приємно мати можливість прийняти більше однієї відповіді :)
sje397

4

Я не знаю, чи відповість це на ваше запитання, але це (Як компілятор C ++ реалізує обробку винятків) - відмінна стаття про обробку винятків взагалі:. Дуже рекомендую (:

Вибачте за коротку відповідь, але вся інформація в статті чудова, я не можу вибрати та розмістити тут деяку інформацію.


Шукайте на excpt_infoцій сторінці, це дає деяку інформацію, як це робить MSVC.
Steve Jessop

1
Це посилання справді описує, як цього не робити в хорошій реалізації. Я знаю, що деякі старіші VC ++ використовували щось подібне, але я сумніваюся, що ви знайдете це в будь-якому сучасному компіляторі.
James Kanze

0

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


-2

Ну, це не може бути в стеку, оскільки це буде розмотано, і воно не може бути в купі, оскільки це означало б, що система, швидше за все, не могла кинути std::bad_alloc. Крім цього, це повністю залежить від реалізації: не зазначена реалізація (яка повинна бути задокументована), але невизначена. (Реалізація може використовувати купу більшу частину часу, якщо вона має якесь резервне резервне копіювання, яке дозволить обмежену кількість винятків, навіть коли пам'яті більше не буде.)


3
Ваша відповідь суперечить сама собі.
Гонки легкості на орбіті

Як? У ній представлені обмеження щодо реалізації, які передбачаються стандартом.
James Kanze

Один приклад: "це не може бути в купі" ... "Реалізація може використовувати купу більшу частину часу". Крім того, вся перша половина відповіді є неправильною, як це пояснюється в інших відповідях (і, справді, частково у другій частині вашої!).
Гонки легкості на орбіті

Стандарт вимагає std::bad_allocроботи. Це означає, що реалізація не може систематично використовувати купу. Стандарт не дозволяє цього. Я базую свої вимоги на стандарті.
James Kanze

FSVO "систематично", це правильно. Однак, як ви зазначаєте у другій частині своєї відповіді, реалізація дійсно може використовувати купу ^ H ^ H ^ H ^ Hfreestore разом із альтернативами.
Гонки легкості на орбіті
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.