Wenn ich etwas "werfe", wo wird es gespeichert?

82

Ich verstehe, dass wenn etwas thrown ist, der Stapel bis zu dem Punkt 'abgewickelt' wird, an dem es abgefangen wird, und die Destruktoren von Klasseninstanzen auf dem Stapel in jedem Funktionskontext ausgeführt werden (weshalb Sie keine Ausnahme von einem Destruktor auslösen sollten - Sie könnten am Ende einen zweiten werfen) ... aber ich frage mich, wo im Speicher das Objekt gespeichert ist, das ich geworfen habe, während dies geschieht?

Ist es implementierungsabhängig? Wenn ja, gibt es eine bestimmte Methode, die von den meisten gängigen Compilern verwendet wird?

sje397
quelle
1
Gute Frage - es wird wahrscheinlich nicht vom Standard spezifiziert, da der Standard nicht einmal sagt, dass Sie einen Stapel haben müssen. Praktisch, vielleicht wird es auf dem Haufen zugewiesen und freigegeben, wenn der Fangblock beendet wird?
Kerrek SB
@ Kerrek: Ich denke eher, dass das Objekt ganz unten auf dem Stapel "unten" platziert wird. Wickeln Sie sich dann nach oben ab, merken Sie sich, wo es war, und raisezerstören Sie es, sobald Sie mit dem Abwickeln fertig sind (einschließlich der Möglichkeit, Fangklauseln die Möglichkeit zu geben, die Ausnahme durch Bezugnahme zu behandeln und sie erneut zu behandeln). Das Problem bei der Heap-Zuweisung ist, was tun Sie, wenn Ihr Versuch, die Ausnahme zuzuweisen, fehlschlägt? Sie müssten abbrechen (genau wie beim Stapelüberlauf, wenn das Ablegen auf dem Stapel aufgrund von Platzmangel "fehlschlägt"). Im Gegensatz zu Java darf die Implementierung stattdessen keine andere Ausnahme auslösen.
Steve Jessop
@Steve: Auch möglich - Kirils Artikel besagt, dass die Ausnahme auf dem Stapel zugeordnet ist und eine Hilfsinfostruktur ihre Adresse und ihren Löscher usw. aufzeichnet, aber ich stelle mir vor, dass Implementierungen dies nach Belieben tun können?
Kerrek SB
@Kerrek: Ja, vorbehaltlich der Einschränkung, dass die Ausnahme tatsächlich ausgelöst werden muss, und des üblichen Problems, dass die Implementierung aufgrund des fehlenden Stapels solchen Verantwortlichkeiten entgehen kann :-) Wenn Sie sie auf den Stapel legen, werden Ausnahmen ausgelöst Aufgrund des statischen Typs des Throw-Ausdrucks kann Ihr Stack-Frame für die gesamte Funktion den zum Auslösen dieses Typs erforderlichen Speicherplatz enthalten, obwohl ich nicht weiß, ob MSVC dies tut.
Steve Jessop

Antworten:

51

Ja, die Antwort ist vom Compiler abhängig.

Ein kurzes Experiment mit meinem Compiler ( g++ 4.4.3) zeigt, dass seine Laufzeitbibliothek zuerst versucht, mallocSpeicher für die Ausnahme zu erstellen, und andernfalls versucht, Speicherplatz in einem prozessweiten "Notfallpuffer" zuzuweisen, der sich im Datensegment befindet. Wenn das nicht klappt, ruft es an std::terminate().

Es scheint, dass der Hauptzweck des Notfallpuffers darin besteht, werfen zu können, std::bad_allocnachdem der Prozess keinen Heap-Speicherplatz mehr hat (in diesem Fall würde der mallocAufruf fehlschlagen).

Die relevante Funktion ist __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));
}

Ich weiß nicht, wie typisch dieses Schema ist.

NPE
quelle
"In diesem Fall würde der mallocAnruf fehlschlagen", normalerweise, aber nicht unbedingt.
Ayxan Haqverdili
20

Von dieser Seite :

Für Ausnahmen, die ausgelöst werden, ist Speicher erforderlich. Dieser Speicher muss bestehen bleiben, während der Stapel abgewickelt wird, da er vom Handler verwendet wird und threadsicher sein muss. Der Speicher für Ausnahmeobjekte wird daher normalerweise im Heap zugewiesen , obwohl Implementierungen möglicherweise einen Notfallpuffer bereitstellen , um das Auslösen von bad_alloc-Ausnahmen unter Bedingungen mit geringem Speicher zu unterstützen.

Dies ist nur der Itanium ABI und ich suche nach Details, die für GCC, Clang und MSVC spezifisch sind. Der Standard spezifiziert jedoch nichts und dies scheint der offensichtliche Weg zu sein, um Ausnahmespeicher zu implementieren, also ...

Leichtigkeitsrennen im Orbit
quelle
1
Suchen Sie noch nach Details? Manchmal wäre es schön, mehr als eine Antwort akzeptieren zu können :)
sje397
4

Ich weiß nicht, ob dies Ihre Frage beantworten wird, aber dies (wie ein C ++ - Compiler die Ausnahmebehandlung implementiert) ist ein ausgezeichneter Artikel über die Ausnahmebehandlung überhaupt :. Ich empfehle es sehr (:

Entschuldigen Sie die kurze Antwort, aber die gesamten Informationen im Artikel sind großartig. Ich kann hier keine Informationen auswählen und veröffentlichen.

Kiril Kirov
quelle
Suchen Sie auf excpt_infodieser Seite nach Informationen darüber, wie MSVC dies tut.
Steve Jessop
1
Dieser Link beschreibt wirklich, wie man es in einer guten Implementierung nicht macht. Ich weiß, dass einige ältere VC ++ so etwas verwendet haben, aber ich bezweifle, dass Sie es in jedem modernen Compiler finden werden.
James Kanze
0

Der C ++ - Standard gibt im Allgemeinen an, wie sich die Sprache verhält, nicht jedoch, wie der Compiler dieses Verhalten implementieren soll. Ich denke, diese Frage fällt in diese Kategorie. Der beste Weg, so etwas zu implementieren, hängt von den Besonderheiten der Maschine ab - einige Prozessoren haben viele Allzweckregister, andere nur sehr wenige. Ein Prozessor kann sogar nur für Ausnahmen mit einem speziellen Register erstellt werden. In diesem Fall sollte der Compiler die Möglichkeit haben, diese Funktion zu nutzen.

Caleb
quelle
-2

Nun, es kann nicht auf dem Stapel sein, da das abgewickelt wird, und es kann nicht auf dem Haufen sein, da dies bedeuten würde, dass das System wahrscheinlich nicht werfen könnte std::bad_alloc. Davon abgesehen liegt es ganz bei der Implementierung: Nicht die Implementierung angegeben (die dokumentiert werden muss), sondern nicht angegeben. (Eine Implementierung kann den Heap die meiste Zeit verwenden, solange sie über eine Art Notfallsicherung verfügt, die eine begrenzte Anzahl von Ausnahmen zulässt, selbst wenn kein Speicher mehr vorhanden ist.)

James Kanze
quelle
3
Ihre Antwort widerspricht sich.
Leichtigkeitsrennen im Orbit
Wie? Es werden die Einschränkungen für eine Implementierung dargestellt, die im Standard enthalten sind.
James Kanze
Ein Beispiel: "Es kann nicht auf dem Heap sein" ... "Eine Implementierung könnte den Heap die meiste Zeit verwenden". Außerdem ist die gesamte erste Hälfte der Antwort falsch, wie in anderen Antworten erläutert (und teilweise auch in Ihrem zweiten Teil!).
Leichtigkeitsrennen im Orbit
Der Standard muss std::bad_allocfunktionieren. Dies bedeutet, dass eine Implementierung den Heap nicht systematisch verwenden kann. Der Standard erlaubt es nicht. Ich stütze meine Ansprüche auf den Standard.
James Kanze
FSVO von "systematisch", das ist richtig. Wie Sie jedoch im zweiten Teil Ihrer Antwort angeben, kann eine Implementierung tatsächlich den Heap ^ H ^ H ^ H ^ Hfreestore in Verbindung mit Alternativen verwenden.
Leichtigkeitsrennen im Orbit