Wird die Verwendung von Goto-Leak-Variablen?

94

Stimmt es, dass gotoüber Codebits gesprungen wird, ohne Destruktoren und Dinge aufzurufen?

z.B

void f() {
   int x = 0;
   goto lol;
}

int main() {
   f();
lol:
   return 0;
}

Wird nicht xdurchgesickert sein?

Leichtigkeitsrennen im Orbit
quelle
Siehe auch : stackoverflow.com/questions/1258201/… (aber ich wollte es von Grund auf sauber machen!)
Lightness Races in Orbit
15
Was heißt "Won't x be leaked"das Der Typ von xist ein integrierter Datentyp. Warum wählst du kein besseres Beispiel?
Nawaz
2
@Nawaz: Das Beispiel ist perfekt so wie es ist. Fast jedes Mal, wenn ich mit jemandem darüber spreche goto, denken sie, dass sogar Variablen für die automatische Speicherdauer irgendwie "durchgesickert" sind. Dass Sie und ich etwas anderes wissen, ist völlig nebensächlich.
Leichtigkeitsrennen im Orbit
1
@ David: Ich stimme zu, dass diese Frage viel sinnvoller ist, wenn die Variable einen nicht trivialen Destruktor hat ... und ich habe in Tomalaks Antwort nachgesehen und ein solches Beispiel gefunden. Während ein intnicht auslaufen kann, kann es außerdem ausgelaufen sein . Zum Beispiel: void f(void) { new int(5); }Lecks an int.
Ben Voigt
Warum ändern Sie die Frage nicht in "Wie wird im angegebenen Beispiel der Code-Ausführungspfad von f () nach main () übertragen, ohne den Stapel und andere Funktionen für die Rückkehr von Funktionen zu löschen? Wäre es wichtig, wenn ein Destruktor erforderlich wäre?" genannt? Ist das gleiche in C? " Würden beide die Absicht der Frage beibehalten und mögliche Missverständnisse vermeiden?
Jack V.

Antworten:

210

Achtung: Diese Antwort bezieht sich auf C ++ nur ; Die Regeln sind in C ganz anders.


Wird nicht xdurchgesickert sein?

Nein auf keinen Fall.

Es ist ein Mythos, bei dem gotoes sich um ein Konstrukt auf niedriger Ebene handelt, mit dem Sie die in C ++ integrierten Scoping-Mechanismen überschreiben können. (Wenn überhaupt, ist dies longjmpmöglicherweise anfällig dafür.)

Berücksichtigen Sie die folgenden Mechanismen, die verhindern, dass Sie mit Etiketten (einschließlich caseEtiketten) "schlechte Dinge" tun .


1. Beschriftungsbereich

Sie können nicht über Funktionen springen:

void f() {
   int x = 0;
   goto lol;
}

int main() {
   f();
lol:
   return 0;
}

// error: label 'lol' used but not defined

[n3290: 6.1/1]:[..] Der Umfang eines Etiketts ist die Funktion, in der es erscheint. [..]


2. Objektinitialisierung

Sie können nicht über die Objektinitialisierung springen:

int main() {
   goto lol;
   int x = 0;
lol:
   return 0;
}

// error: jump to label ‘lol’
// error:   from here
// error:   crosses initialization of ‘int x’

Wenn Sie über die Objektinitialisierung zurückspringen , wird die vorherige "Instanz" des Objekts zerstört :

struct T {
   T() { cout << "*T"; }
  ~T() { cout << "~T"; }
};

int main() {
   int x = 0;

  lol:
   T t;
   if (x++ < 5)
     goto lol;
}

// Output: *T~T*T~T*T~T*T~T*T~T*T~T

[n3290: 6.6/2]:[..] Die Übertragung aus einer Schleife, aus einem Block oder zurück an einer initialisierten Variablen mit automatischer Speicherdauer vorbei beinhaltet die Zerstörung von Objekten mit automatischer Speicherdauer, die an dem Punkt, der von übertragen wurde, aber nicht an dem Punkt, an den übertragen wurde, in den Geltungsbereich fallen . [..]

Sie können nicht in den Bereich eines Objekts springen, auch wenn es nicht explizit initialisiert wurde:

int main() {
   goto lol;
   {
      std::string x;
lol:
      x = "";
   }
}

// error: jump to label ‘lol’
// error:   from here
// error:   crosses initialization of ‘std::string x’

... mit Ausnahme bestimmter Arten von Objekten , mit denen die Sprache unabhängig umgehen kann, da sie keine "komplexe" Konstruktion erfordern:

int main() {
   goto lol;
   {
      int x;
lol:
      x = 0;
   }
}

// OK

[n3290: 6.7/3]:Es ist möglich, in einen Block zu übertragen, jedoch nicht so, dass Deklarationen bei der Initialisierung umgangen werden. Ein Programm, das von einem Punkt, an dem sich eine Variable mit automatischer Speicherdauer nicht im Gültigkeitsbereich befindet, zu einem Punkt springt, an dem sie sich im Gültigkeitsbereich befindet, ist fehlerhaft, es sei denn, die Variable hat einen Skalartyp, einen Klassentyp mit einem trivialen Standardkonstruktor und einen trivialen Destruktor, a cv-qualifizierte Version eines dieser Typen oder ein Array eines der vorhergehenden Typen und wird ohne Initialisierer deklariert. [..]


3. Das Springen hält sich an den Umfang anderer Objekte

Ebenso Objekte mit automatischer Speicherdauer sind nicht „durchgesickert“ , wenn Sie gotoaus ihrem Umfang :

struct T {
   T() { cout << "*T"; }
  ~T() { cout << "~T"; }
};

int main() {
   {
      T t;
      goto lol;
   }

lol:
   return 0;
}

// *T~T

[n3290: 6.6/2]:Beim Verlassen eines Bereichs (wie auch immer ausgeführt) werden Objekte mit automatischer Speicherdauer (3.7.3), die in diesem Bereich erstellt wurden, in umgekehrter Reihenfolge ihrer Erstellung zerstört. [..]


Fazit

Die oben genannten Mechanismen stellen sicher, dass gotoSie die Sprache nicht brechen können.

Natürlich bedeutet dies nicht automatisch, dass Sie es gotofür ein bestimmtes Problem "verwenden" sollten , aber es bedeutet , dass es bei weitem nicht so "böse" ist, wie der verbreitete Mythos die Menschen glauben macht.

Leichtigkeitsrennen im Orbit
quelle
8
Möglicherweise stellen Sie fest, dass C nicht verhindert, dass all diese gefährlichen Dinge passieren.
Daniel
13
@ Daniel: Die Frage und Antwort beziehen sich sehr speziell auf C ++, sind aber fair. Vielleicht können wir eine weitere FAQ haben, die den Mythos zerstreut, dass C und C ++ gleich sind;)
Leichtigkeitsrennen im Orbit
3
@Tomalak: Ich glaube nicht, dass wir hier nicht einverstanden sind. Viele der auf SO gegebenen Antworten sind irgendwo explizit dokumentiert. Ich habe nur darauf hingewiesen, dass es für einen C-Programmierer verlockend sein könnte, diese Antwort zu sehen und anzunehmen, dass sie in C ++ ähnlich funktionieren sollte, wenn sie in C ++ funktioniert
Daniel
2
Vielleicht möchten Sie auch hinzufügen, dass all diese Dinge, die über die Initialisierung springen, für Fallbezeichnungen gleich sind.
PlasmaHH
12
Wow, ich hatte gerade angenommen, dass die Semantik von C ++ für goto gebrochen war, aber sie ist überraschend vernünftig! Gute Antwort.
Joseph Garvin