Czy używanie zmiennych wycieku goto?

94

Czy to prawda, że gotoprzeskakuje przez fragmenty kodu bez wywoływania destruktorów i innych rzeczy?

na przykład

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

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

Nie xwycieknie?

Wyścigi lekkości na orbicie
źródło
Związane z: stackoverflow.com/questions/1258201/ ... (ale chciałem zrobić to od zera, czysto!)
Wyścigi lekkości na orbicie
15
Co to "Won't x be leaked"znaczy? Typ xto wbudowany typ danych. Dlaczego nie wybierzesz lepszego przykładu?
Nawaz,
2
@Nawaz: Przykład jest doskonały. Niemal za każdym razem, gdy z kimś rozmawiam goto, myślą, że nawet zmienne automatycznego czasu przechowywania są w jakiś sposób „wyciekające”. To, że ty i ja wiemy, że jest inaczej, nie ma znaczenia.
Wyścigi lekkości na orbicie,
1
@David: Zgadzam się, że to pytanie ma dużo więcej sensu, gdy zmienna ma nietrywialnego destruktora ... i szukałem odpowiedzi Tomalaka i znalazłem taki przykład. Ponadto, chociaż intjakiś nie może przeciekać, może przeciekać . Na przykład: void f(void) { new int(5); }przecieki int.
Ben Voigt,
Dlaczego nie zmienić pytania na coś w rodzaju: „Czy w podanym przykładzie ścieżka wykonania kodu zostanie przeniesiona z f () do main () bez czyszczenia stosu i innych funkcji powrotu z funkcji? Czy miałoby znaczenie, czy destruktor musiałby być nazywany? Czy jest taki sam w C? ” Czy to jedno i drugie podtrzymałoby intencję pytania, a jednocześnie pozwoliłoby uniknąć możliwych nieporozumień?
Jack V.

Odpowiedzi:

210

Ostrzeżenie: ta odpowiedź dotyczy tylko języka C ++ ; zasady są zupełnie inne w C.


Nie xwycieknie?

Nie, absolutnie nie.

To mit, który gotojest konstrukcją niskiego poziomu, która pozwala przesłonić wbudowane mechanizmy określania zakresu C ++. (Jeśli już, to longjmpmoże być na to podatny).

Rozważ następujące mechanizmy, które zapobiegają robieniu „złych rzeczy” z etykietami (w tym caseetykietami).


1. Zakres etykiety

Nie możesz przeskakiwać między funkcjami:

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

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

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

[n3290: 6.1/1]:[..] Zakres etykiety to funkcja, w której się pojawia. [..]


2. Inicjalizacja obiektu

Nie możesz przeskoczyć przez inicjalizację obiektu:

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

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

Jeśli przeskoczysz z powrotem przez inicjalizację obiektu, poprzednia „instancja” obiektu zostanie zniszczona :

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]:[..] Przeniesienie z pętli, z bloku lub z powrotem poza zainicjowaną zmienną z automatycznym czasem przechowywania obejmuje zniszczenie obiektów z automatycznym czasem przechowywania, które są w zakresie w punkcie przeniesionym z, ale nie w punkcie przeniesionym do . [..]

Nie możesz wskoczyć w zakres obiektu, nawet jeśli nie jest on jawnie zainicjowany:

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

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

... z wyjątkiem pewnych rodzajów obiektów , z którymi język może sobie poradzić niezależnie, ponieważ nie wymagają one „skomplikowanej” konstrukcji:

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

// OK

[n3290: 6.7/3]:Możliwe jest przeniesienie do bloku, ale nie w sposób omijający deklaracje przy inicjalizacji. Program, który przeskakuje z punktu, w którym zmienna z automatycznym czasem przechowywania nie znajduje się w zakresie, do punktu, w którym jest w zakresie, jest źle sformułowana, chyba że zmienna ma typ skalarny, typ klasy z trywialnym konstruktorem domyślnym i trywialnym destruktorem, a cv kwalifikowana wersja jednego z tych typów lub tablica jednego z powyższych typów i jest zadeklarowana bez inicjatora. [..]


3. Skakanie podlega zakresowi innych obiektów

Podobnie, obiekty z automatycznym czas przechowywania nie „wyciekły”, gdy gotoz ich zakresem :

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

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

lol:
   return 0;
}

// *T~T

[n3290: 6.6/2]:Po wyjściu z zakresu (jakkolwiek zrealizowane) obiekty z automatycznym czasem przechowywania (3.7.3), które zostały zbudowane w tym zakresie, są niszczone w odwrotnej kolejności do ich budowy. [..]


Wniosek

Zapewniają to powyższe mechanizmy goto nie pozwoli ci to złamać języka.

Oczywiście, nie oznacza automatycznie, że „powinien” stosowanie gotodla danego problemu, ale to nie znaczy, że nie jest aż tak „zło” jako wspólny odprowadzeniach mit ludzi do wiary.

Wyścigi lekkości na orbicie
źródło
8
Możesz zauważyć, że C nie zapobiega tym wszystkim niebezpiecznym rzeczom.
Daniel,
13
@Daniel: Pytanie i odpowiedź bardzo konkretnie dotyczą C ++, ale słuszna uwaga. Może moglibyśmy mieć kolejne FAQ, które rozwiałyby mit, że C i C ++ to to samo;)
Lightness Races in Orbit
3
@Tomalak: Nie sądzę, że się tutaj nie zgadzamy. Wiele odpowiedzi udzielonych na SO jest gdzieś wyraźnie udokumentowanych. Zwróciłem tylko uwagę, że programista C może kusić, aby zobaczyć tę odpowiedź i założyć, że jeśli działa w C ++, to powinno działać podobnie w C.
Daniel,
2
Możesz również chcieć dodać, że wszystkie te przeskakiwanie rzeczy inicjalizacji są takie same dla etykiet skrzynek.
PlasmaHH
12
Wow, właśnie założyłem, że semantyka C ++ została zepsuta dla goto, ale są zaskakująco rozsądne! Świetna odpowiedź.
Joseph Garvin,