Goto?



  • Ich würde gerne aus einer for Schleife frühzeitig rausspringen wenn er das passende gefunden hat. Nun habe ich im Internet gesucht und haben den goto Befehl gefunden. Aber es steht oft man sollte den nicht benutzen.
    Was meint ihr?



  • Dafür gibt es break.



  • Ich meine, dass break genau dafür erfunden wurde. Oder vielleicht noch besser return.



  • Es heißt, Goto sei böse, aber man benutzt es aus Effizienzgründen immer noch bei der Kernelprogrammierung. Siehe Linux-Kernel.

    L. G.,
    IBV


  • Mod

    Oder vielleicht noch besser return.

    Nein, das ist eine furchtbare Idee. Immer break . Ich bin mir hundert prozentig sicher dass mir volkard hier zustimmt.

    IBV schrieb:

    Es heißt, Goto sei böse, aber man benutzt es aus Effizienzgründen immer noch bei der Kernelprogrammierung.

    Das Böse-sein ist ein Hinweis an Anfänger, ein Feature nicht zu verwenden bevor man nicht genau seine Fallen kennt.



  • Arcoth schrieb:

    Immer break . Ich bin mir hundert prozentig sicher dass mir volkard hier zustimmt.

    Also bei meheren verschachtelten for-Schleifen kann es mit break aber auch ganz schön nervig und unübersichtlich werden:

    bool flag_2_loop = false, flag_3_loop = false;
    for (size_t i = 0; i < 10; i++) {
    	for (size_t j = 0; j < 10; j++) {
    		for (size_t k = 0; k < 10; k++) {
    			if (i + j + k == 25) {
    				flag_3_loop = true;
    				break;
    			}
    		}
    		if (flag_3_loop) {
    			flag_2_loop = true;
    			break;
    		}
    	}
    	if (flag_2_loop) {
    		break;
    	}
    }
    

    vs:

    for (size_t i = 0; i < 10; i++) {
    	for (size_t j = 0; j < 10; j++) {
    		for (size_t k = 0; k < 10; k++) {
    			if (i + j + k == 25) {
    				goto end_of_loop;
    			}
    		}
    	}
    }
    end_of_loop:
    

    Leider kann man bei nem break ja nicht mit angeben aus wievielen Schleifen er rausspringen soll. Sowas wie break(3); oder so wäre hier vielleicht ganz cool?

    Zumindest hatte ich dieses Problem schon öfters und habs immer mit flags gelöst um ja das goto zu vermeiden, das macht die Schleifen aber schon sehr häßlich und vermutlich auch etwas ineffizienter da ja in jedem Durchgang das Flag gecheckt werden muss...



  • So viele Antworten 😃
    Ich habe das nun mit break; gelöst. Und die finger lasse ich dann von goto auch, aber warum gibt es dann das überhaupt nocht? Weil ich finde es eig. ja ganz praktisch.



  • Zum einen ist der Kernel in C geschrieben, nicht in C++, und zum anderen ist das auch eher ein Artefakt des Umstandes, dass die Coding Guidelines vor 20 Jahren geschrieben wurden, als der Effizienzgrund noch griff -- das hat sich durch optimierende Compiler seit langer Zeit erledigt.

    Außerdem benutzt man es auch da nur zur Fehlerbehandlung und nicht, um aus Schleifen auszubrechen; das wird da als eine Art Exception-Ersatz ge- bzw. missbraucht. In C++ gibt es bessere Mittel (RAII und Exceptions), und auch in C ist das ein Ansatz, der sich zum Beispiel angesichts der Kaskade von goto fail-Bugs in diversen SSL-Implementationen in letzter Zeit aus meiner Sicht nicht so richtig bewährt hat. Lieber noch ne Funktion einziehen und bei Performancebedenken static inline davorschreiben; der Compiler regelt das schon.

    Was das Ausbrechen aus Schleifen angeht: Mindestens kann man in allen Schleifen *ein* Flag prüfen, man braucht nicht mehrere, die gleichzeitig den Wert wechseln. Es ist mir aber auch noch nie passiert, dass ich geschachtelte Schleifen hatte, ohne dass sich eine logische Trennung in ein oder mehrere eigene Funktionen aufdrängte. Im Zweifel schreibe ich etwas in dieser Art:

    template<typename T, std::size_t X, std::size_t Y, std::size_t Z>
    T *find_3d(T (&haystack)[X][Y][Z], T const &needle) {
      for(std::size_t i = 0; i < X; ++i) {
        for(std::size_t j = 0; j < Y; ++j) {
          for(std::size_t k = 0; k < Z; ++k) {
            if(haystack[i][j][k] == needle) {
              return &haystack[i][j][k];
            }
          }
        }
      }
    
      return nullptr;
    }
    

    ...das ist jetzt ein sehr simplistisches und nicht besonders realitätsnahes Beispiel, aber es verdeutlicht hoffentlich das Prinzip. Wenn man die Schleife unbedingt lokal haben will, kann man da ein Lambda drumschustern, aber erfahrungsgemäß sieht der Code gleich viel übersichtlicher aus, wenn man ihn entlang solcher Linien aufteilt. Und mit dem Lambda-Trick kämen wir in den Graubereich der Frage, welcher Hack weniger dreckig ist.



  • IBV schrieb:

    Es heißt, Goto sei böse, aber man benutzt es aus Effizienzgründen immer noch bei der Kernelprogrammierung. Siehe Linux-Kernel.
    L. G.,
    IBV

    Falsches Forum. In C (also auch im Kernel) braucht man break. In C++ würde ich das nicht so behaupten.



  • Arcoth schrieb:

    Oder vielleicht noch besser return.

    Nein, das ist eine furchtbare Idee. Immer break . Ich bin mir hundert prozentig sicher dass mir volkard hier zustimmt.

    100% falsch!
    In C++ ist return viel viel toller als break.


  • Mod

    volkard schrieb:

    Arcoth schrieb:

    Oder vielleicht noch besser return.

    Nein, das ist eine furchtbare Idee. Immer break . Ich bin mir hundert prozentig sicher dass mir volkard hier zustimmt.

    100% falsch!
    In C++ ist return viel viel toller als break.

    Was!?

    Du willst mir erzählen,

    void foo()
    {
        Schleife
            If DasundDas
                return;
    }
    

    Ist besser als

    void foo()
    {
        Schleife
            If DasundDas
                break;
    }
    

    ? Das ist nämlich was ich meine.



  • Arcoth schrieb:

    Was!?

    Break und return haben eine ganz andere Bedeutung. return heißt, dass die Aufgabe hier abgeschlossen ist und die Funktion den Wert (kann auch void sein) zurückgibt.
    break hingegen ist nichts anderes als ein eingeschränktes goto. Meistens braucht man es in C als zusätzliche Abbruchbedingung, wenn man z.B. etwas sucht. In C++ hat man dank templates aber high-order-functions und kann mit find_if, equals, any_of oder eigenen Funktionen arbeiten, die intern mit return beenden.
    Ich verwende break eigentlich nur dann, wenn sich der Aufwand für einen iterator nicht lohnt oder ich fremden Code einbauen muss.


  • Mod

    Break und return haben eine ganz andere Bedeutung. return heißt, dass die Aufgabe hier abgeschlossen ist und die Funktion den Wert (kann auch void sein) zurückgibt.

    Na genau das meine ich doch! Warum soll return hier pauschal besser sein (nach SG1' Zitat)? Um Schleifen abzubrechen nimmt man break. Auch wenn danach die Prozedur beendet ist.



  • Wie schon geschrieben hilft ein break bei doppelten Schleifen nicht - dann lieber kurze Funktionen und ein return verwenden.
    Häufig will man ja dann auch unterschiedliche Rückgabewerte haben (z.B. true oder false oder bei einer Suchschleife den gefunden Wert bzw. einen Defaultwert) - auch wenn man solche Schleifen wohl gegen eine Algorithmus-Funktion austauschen sollte.



  • zum Entkommen aus mehrfach geschachtelten Schleifen kann man die betreffenden Lauf-Variablen so setzen, daß die Schleifenbedingung nicht mehr erfüllt ist. Also bspw

    i = foo; j = bar;

    zum Entkommen aus dem Inneren von

    for(i = 0; i < foo; ++i){ for(j = 0; j < bar; ++j){ ... } }

    Ob das schön ist, sei mal dahingestellt 🙂



  • Arcoth schrieb:

    Break und return haben eine ganz andere Bedeutung. return heißt, dass die Aufgabe hier abgeschlossen ist und die Funktion den Wert (kann auch void sein) zurückgibt.

    Na genau das meine ich doch! Warum soll return hier pauschal besser sein (nach SG1' Zitat)? Um Schleifen abzubrechen nimmt man break. Auch wenn danach die Prozedur beendet ist.

    Richtig. Man nimmt das, was man ausdrücken möchte.

    Nur würde ich sagen dass man, in einem Fall wo man wirklich 1:1 return gegen break austauschen kann, normalerweise auch "Funktion fertig" ausdrücken möchte und nicht "Schleife fertig".
    Weil das return dabei normalerweise innerhalb eines if steht, dessen Bedingung dem "fertig sein mit der Funktion" entspricht.

    ps: Meist hat man sowieso auch noch Returnwerte. Und in dem Fall finde ich break sowieso furchtbar, weil sehr umständlich.
    Vergleiche

    int FindFooIndex(int foo)
    {
        int index = -1; // Not found
        for (...)
        {
            if (...)
            {
                index = ...;
                break;
            }
        }
        return index;
    }
    
    // vs.
    
    int FindFooIndex(int foo)
    {
        for (...)
            if (...)
                return ...;
    
        return -1; // Not found
    }
    

    Oder gar

    int GetFooBar(int foo)
    {
        bool found = false;
        int bar;
        for (...)
        {
            if (...)
            {
                bar = ...;
                found = true;
                break; // Könnte man in diesem Beispiel auch als "&& !found" in die Laufbedingung verschieben
            }
        }
        if (found)
            return bar;
        else
            throw ...;
    }
    
    // vs.
    
    int GetFooBar(int foo)
    {
        for (...)
            if (...)
                return ...;
    
        throw ...;
    }
    


  • großbuchstaben schrieb:

    zum Entkommen aus mehrfach geschachtelten Schleifen kann man die betreffenden Lauf-Variablen so setzen, daß die Schleifenbedingung nicht mehr erfüllt ist.

    Brr. So einen Hirnfick wie Laufvariablen verändern, nur um ein Goto zu vermeiden? Ich dachte der Grund für die Ablehnung von Goto läge in dessen Potenzial, zum Schreiben schwer nachvollziehbaren Codes verwendet zu werden. Ein "goto out" ist aber doch erfrischend klar, verglichen mit künstlichen Flags oder gar Rumgefummel an den Schleifenzählern.



  • @goto *(&&label +
    Bin grundsätzlich ganz deiner Meinung.

    Bis auf das kleine Detail, dass ich in den letzten 5~10 Jahren weder goto noch goto -Umgehung durch Verändern von Laufvariablen gebraucht habe.
    (Und natürlich auch sonst keine schmutzigen Tricks wie Exceptions zu werfen o.ä.)

    Alle Fälle wo man das eine oder andere zu brauchen meinen könnte, haben sich anders (mMn. "schöner"/"eleganter") lösen lassen. Meist durch das Rausziehen der verschachtelten Schleifen in eine eigene Funktion, so dass man dann return als "multi break" verwenden kann.
    Manchmal auch durch das "Zerteilen" der verschachtelten Schleifen in mehrere Funktionen die dann jeweils nur mehr eine der Schleifen enthalten.



  • volkard schrieb:

    In C++ ist return viel viel toller als break.

    Einwand: return verlässt sofort die aktuelle Funktion. Man kann bei verschachtelten Schleifen dann in der Funktion nicht fortfahren. Vielleicht doch besser mit break.

    Hier ein Beispiel in C# (düfte in C++ geringfügig anders aussehen)

    private void Form1_Load(object sender, EventArgs e)
      {
          int  i=0, j=0, k=0;
          bool exit = false;
    
          for (i = 0; i < 50; i++)
          {
              if (exit) break;
              for (j = 0; j < 50; j++)
              {
                   if (exit) break;
                   for (k = 0; k < 50; k++)
                   {
                   // Abbruch einer verschachtelten Schleife
                      if ((i==6) & (j==3) & (k == 10))
                      {
                          exit = true;
                          i--;
                          j--;
                          break;
                          //return;
                      }
                   }
               }
          }
          // mit return kommt man hier überhaupt nicht hin!
          // mit break kann man hier aber nach dem Abbruch weiterarbeiten!
          MessageBox.Show("exit    " + exit.ToString() +    // TRUE
                          "\ni         " + i.ToString() +   // 6
                          "\nj         " + j.ToString() +   // 3
                          "\nk         " + k.ToString());   // 10 
       }
    

    Geht doch allein mit break! 🕶 GOTO ist in jedem Fall mega out! 😡



  • Geht auch gut mit return

    void xXx::Form1_Load(object & sender, EventArgs & e)
    {
    	int  i=0, j=0, k=0;
    
    	auto lmbd = [&]()
    	{
    		for (i = 0; i < 50; i++)
    		{
    			for (j = 0; j < 50; j++)
    			{
    
    				for (k = 0; k < 50; k++)
    				{
    					// Abbruch einer verschachtelten Schleife
    					if ((i==6) & (j==3) & (k == 10))
    					{
    						return;
    					}
    				}
    			}
    		}
    	}();
    
    	// mit return kommt man hier sehr gut  hin!
    	// und man kann hier auch nach dem Abbruch weiterarbeiten!
    	MessageBox.Show("exit    " + exit.ToString() +    // TRUE
    	"\ni         " + i.ToString() +   // 6
    	"\nj         " + j.ToString() +   // 3
    	"\nk         " + k.ToString());   // 10
    }
    

    (Wenn man den Code mal dümmlich als C++ ansieht, aber ich denekd as Prinzip sollte klar sein...)

    PS: Das mit dem Break nach dem if, dass da nicht die Zeile umgebrochen ist: Das ist ne Todsünde. Mindestens genauso wie mein return 0x0; 😃
    Trollololol


Log in to reply