Kiedy coś wrzucę, gdzie jest to przechowywane w pamięci?


82

Rozumiem, że gdy coś jest thrown, stos jest `` rozwijany '' do punktu, w którym jest przechwytywany, a destruktory instancji klas na stosie w każdym kontekście funkcji są uruchamiane (dlatego nie należy rzucać wyjątku z destruktora - mógłbyś skończyć rzuceniem drugiego) ... ale zastanawiam się, gdzie w pamięci jest przechowywany obiekt, który rzuciłem, gdy to się dzieje?

Czy jest to zależne od implementacji? Jeśli tak, to czy jest jakaś konkretna metoda używana przez najpopularniejsze kompilatory?


1
Dobre pytanie - prawdopodobnie nie jest to określone w standardzie, ponieważ standard nie mówi nawet, że musisz mieć stos. Praktycznie, może jest przydzielany na stercie i zwalniany, gdy blok catch zostanie zamknięty?
Kerrek SB,

@Kerrek: Myślę, że bardziej prawdopodobne jest, że obiekt jest umieszczony na stosie na samym dole. Następnie rozwijaj się w górę, pamiętając, gdzie to było, a po zakończeniu rozwijania (w tym dając wszystkim klauzulom catch możliwość odniesienia się do wyjątku przez odniesienie i ponownego użycia raise), zniszcz go. Problem z alokacją sterty polega na tym, co zrobić, jeśli próba przydzielenia wyjątku nie powiedzie się? Musiałbyś przerwać (tak jak w przypadku przepełnienia stosu, jeśli umieszczenie go na stosie „nie powiedzie się” z powodu braku miejsca). W przeciwieństwie do Java implementacja nie może zamiast tego zgłosić innego wyjątku.
Steve Jessop,

@Steve: Również możliwe - artykuł Kirila mówi, że wyjątek jest przydzielany na stosie, a pomocnicza struktura informacji rejestruje jego adres i usuwanie itp., Ale wyobrażam sobie, że implementacje mogą to robić w dowolny sposób?
Kerrek SB,

@Kerrek: tak, z zastrzeżeniem, że musi faktycznie wyrzucić wyjątek, a zwykły problem, że brak stosu pozwala implementacji uniknąć takich obowiązków :-) Jeśli umieścisz go na stosie, to ponieważ wyjątki są wyrzucane według statycznego typu wyrażenia throw, ramka stosu dla całej funkcji może zawierać dowolną przestrzeń potrzebną do wyrzucenia tego typu, chociaż nie wiem, czy MSVC to robi.
Steve Jessop,

Odpowiedzi:


51

Tak, odpowiedź zależy od kompilatora.

Szybki eksperyment z moim kompilatorem ( g++ 4.4.3) ujawnia, że ​​jego biblioteka mallocuruchomieniowa najpierw próbuje zapamiętać wyjątek i, w przypadku niepowodzenia, próbuje przydzielić miejsce w „buforze awaryjnym” obejmującym cały proces, który żyje w segmencie danych. Jeśli to nie zadziała, dzwoni std::terminate().

Wydawałoby się, że głównym celem bufora awaryjnego jest możliwość wyrzucenia std::bad_allocpo tym, jak procesowi zabraknie miejsca na stercie (w takim przypadku mallocwywołanie zakończy się niepowodzeniem).

Odpowiednią funkcją jest __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));
}

Nie wiem, jak typowy jest ten schemat.


„w takim przypadku mallocpołączenie zakończy się niepowodzeniem”, zwykle, ale niekoniecznie.
Ayxan Haqverdili

20

Z tej strony :

Magazyn jest potrzebny do zgłaszania wyjątków. Ten magazyn musi trwać podczas rozwijania stosu, ponieważ będzie używany przez procedurę obsługi i musi być bezpieczny dla wątków. Magazyn obiektów wyjątków będzie zatem zwykle przydzielany w stercie , chociaż implementacje mogą zapewnić bufor awaryjny do obsługi zgłaszania wyjątków bad_alloc w warunkach małej ilości pamięci.

Teraz to tylko Itanium ABI i szukam szczegółów specyficznych dla GCC, Clang i MSVC. Jednak standard niczego nie określa i wydaje się, że jest to oczywisty sposób implementacji przechowywania wyjątków, więc ...


1
Wciąż szukasz szczegółów? Czasami fajnie byłoby móc zaakceptować więcej niż jedną odpowiedź :)
sje397

4

Nie wiem, czy to odpowie na twoje pytanie, ale ten (Jak kompilator C ++ implementuje obsługę wyjątków) jest w ogóle doskonałym artykułem o obsłudze wyjątków:. Gorąco polecam (:

Przepraszam za krótką odpowiedź, ale cała informacja w artykule jest świetna, nie mogę tutaj wybrać i opublikować niektórych informacji.


Wyszukaj na excpt_infotej stronie, daje trochę informacji, jak robi to MSVC.
Steve Jessop,

1
Ten link naprawdę opisuje, jak tego nie robić w dobrej implementacji. Wiem, że jakiś starszy VC ++ używał czegoś takiego, ale wątpię, czy znajdziesz to w jakimkolwiek nowoczesnym kompilatorze.
James Kanze,

0

Standard C ++ ogólnie określa, jak zachowuje się język, ale nie określa, jak kompilator powinien zaimplementować to zachowanie. Myślę, że to pytanie należy do tej kategorii. Najlepszy sposób na zaimplementowanie czegoś takiego zależy od specyfiki maszyny - niektóre procesory mają wiele rejestrów ogólnego przeznaczenia, inne mają ich niewiele. Procesor może być nawet zbudowany ze specjalnym rejestrem tylko dla wyjątków, w którym to przypadku kompilator powinien mieć swobodę korzystania z tej funkcji.


-2

Cóż, nie może być na stosie, ponieważ zostanie rozwinięty i nie może znajdować się na stercie, ponieważ oznaczałoby to, że system prawdopodobnie nie mógł rzucić std::bad_alloc. Poza tym wszystko zależy od implementacji: nie określono implementacji (co musi być udokumentowane), ale nie określono. (Implementacja może używać sterty przez większość czasu, o ile ma jakąś awaryjną kopię zapasową, która pozwoli na ograniczoną liczbę wyjątków, nawet jeśli nie ma już pamięci).


3
Twoja odpowiedź zaprzecza samej sobie.
Wyścigi lekkości na orbicie,

W jaki sposób? Przedstawia ograniczenia implementacji, które są domniemane w standardzie.
James Kanze,

Jeden przykład: „nie może znajdować się na stercie”… „Implementacja może używać sterty przez większość czasu”. Ponadto cała pierwsza połowa odpowiedzi jest błędna, co wyjaśniono w innych odpowiedziach (a nawet częściowo w drugiej części twojej!).
Wyścigi lekkości na orbicie,

Standard wymaga std::bad_allocdziałania. Oznacza to, że implementacja nie może systematycznie używać sterty. Norma na to nie pozwala. Opieram swoje roszczenia na standardzie.
James Kanze,

FSVO „systematycznie”, to prawda. Jednak, jak stwierdzisz w drugiej części odpowiedzi, implementacja może rzeczywiście używać sterty ^ H ^ H ^ H ^ Hfreestore w połączeniu z alternatywami.
Wyścigi lekkości na orbicie,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.