goto in solchen Fällen unvermeidlich?



  • knivil schrieb:

    die einzige Lösung

    Nein.

    Welche andere Möglichkeit gibt's in solchen Fällen?

    if, else

    hHost CreatePortier(HostPhone MsgFunc){ 
        if(ePortier != NULL || !MsgFunc) 
            return NULL; 
    
        if(!DLInit){ 
            if(!wsDLInit()) 
                return NULL; 
            DLInit = 1; 
        } 
    
        if(!(ePortier = AllocPortier())){ /* Ersatz für 1. Goto */
            DLUnInit();
            return NULL;
        }
    
        ePortier->myPort = PORTIER_PORT; 
        ePortier->OnRecv = MsgFunc; 
        ePortier->mySock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
        if(ePortier->mySock == INVALID_SOCKET){ /* Ersatz für 2. Goto */
            DeAllocPortier();
            DLUnInit();
            ePortier = NULL;
            return NULL;
        }
    
        ePortier->OnRecv = MsgFunc; 
        if(!BindPortier(PORTIER_PORT)){ /* Ersatz für 3. Goto */
            closesocket(ePortier->mySock);
            DeAllocPortier();
            DLUnInit();
            ePortier = NULL;
            return NULL;
        }
    
        return ePortier; 
    }
    

    Also mE sieht es noch unbeholfener aus als das goto, wenn ich bei jedem
    weiteren if dann immer noch längere Freigebe-Blöcke anhängen muss, bei
    denen sich noch dazu die Anweisungsreihenfolge von if zu if immer nur
    geringfügig ändert.
    Wie sähe das denn erst bei einer Initialisierungsfunktion aus, die 20
    System-Resourcen anfordert, und wo dann die 19. Alloktion schief geht?
    Da hätte man dann ja einen unglaublich ineffizienten if-Block dran-zuhängen,
    um die 18 vorhergehenden Allokationen wieder rückgängig zu machen. Und das
    Schlimmste dabei: Der 19. if-Freigabe-Block sähe (bis auf 1-2 Zeilen) genauso
    aus wie der 18. if-Freigabe-Block.
    Also mE vermeidet das goto ziemlich gut redundanten Code, den man andernfalls
    mit if- schreiben müsste. Warum ist also das goto trotzdem so schlecht?

    Mein optimierender Compiler bastelt derartige Funktionen
    oft aus eigenem Antrieb in Near Jumps zu mehreren Labels am Ende der Funktion
    um (m.E. auch gar nicht so unlogisch, wahrscheinlich tut er das damit nicht so
    viel redundante Code-Zeilen in den if-Blöcken stehen).

    Ist es eigentlich generell keine gute Idee, derartige (bestimmt gut gemeinte)
    compilerseitige Optimierungen (im Assembler-Code stehen da oft unglaublich
    viele Jumps) in den eigenen Programmier-Stil aufzunehmen ?
    Weil der Compiler müsste es ja am besten wissen, was optimaler Code ist, und
    wenn der zu diesem Zweck so viele Near Jumps generiert, dann könnte man
    sich diesen Trick ja auch vom Compiler abgucken und selber viele, viele gotos verwenden ? Scheinbar ist das ja laufzeittechnisch (laut Compiler) recht gut?



  • Also meine eigene bescheidene Meinung ist dass goto's nur böse sind weil sie bei sehr häufigem Gebrauch recht unübersichtlich werden. In diesem Fall aber finde ich das absolut legitim und wesentlich(!) übersichtlicher als jedes if/else Konstrukt usw.
    Goto's sollten für einen Computer doch schließlich das natürlichste überhaupt sein 😉
    Aber ist halt auch immer persönlicher Geschmack. Es wird hier immer Gegner geben.



  • Errorhandling wird gern mit gotos gemacht und selbst Linus findet das am besten so: http://kerneltrap.org/node/553/2131
    Seine Argumente lauten im Wesentlichen, dass ein if nur ein strukturiertes goto ist¹, und dass goto die Grundstruktur des Codes sauberer hält, weil das Handling ausgelagert ist und vor allem nicht eingezogen werden muss.

    Dein Beispiel würde mindestens vier ifs entsprechen, das gäben bei bei deiner Breite (4 Leerzeilen, im Vergleich zu 8 bei richtigen Programmen) (4+1)*4=20 Leerzeichen vor jeder Zeile. Das Problem ist, dass eigentlich nichts verschachtelt ist, die ganzen ifs lenken nur ab.

    ¹: Entweder habe ich Gotthard3 falsch verstanden oder er meint, dass ifs etwas anderes wie gotos im Assemblercode sind.



  • Da muss ich wohl auch noch meinen Senf dazu geben.
    bei grossen Funktionen kann man auch einen switch case: nehmen, womit man dann auch bei einem Fehler in einen case springen kann, wenn etwas schiefgehen sollte.
    Goto versuche ich wenn möglich zu vermeiden, da man von C gewönt ist von oben nach unten zu schreiben bzw lesen. Wenn man die ganze zeit im Code suchen muss wo es weiter geht ist das nicht sehr produktiv. Es ist ausserdem auch für aussenstehende einfacher den Code zu lesen, als wenn man 10 goto in einer Funktion hat.



  • Du arbeitest neben goto auch offensichtlich noch mit globalen Variablen, gewöhne dir am besten auch ab (nicht an). Verschiebe deine Aufräumjobs in den aufrufenden Kontext und durchlauf ihn im Fehlerfall:

    returnwert = funktion(parameter...);
    if( returnwert == fehler )
    {
      aufräumen;
    }
    

    Auch eine extra Funktion zum Aufräumen könntest du dir spendieren, es wird weniger Code, übersichtlicher und vor allem: du hast den Aufräumcodeblock nur einmal.



  • gotoerr schrieb:

    Errorhandling wird gern mit gotos gemacht und selbst Linus findet das am besten so: http://kerneltrap.org/node/553/2131
    Seine Argumente lauten im Wesentlichen, dass ein if nur ein strukturiertes goto ist¹

    Wirklich durchdacht ist dieses Argument nicht. Sicher ist ein if, wenn man so will, ein strukturiertes goto. Genauso wie while, switch, break, return usw. Das geht aber völlig am Thema vorbei, das schlechte am goto ist ja nicht der Sprung im Kontrollfluss, sondern gerade das unstrukturierte.



  • no rocket science:

    hHost CreatePortier(HostPhone MsgFunc)
    {
    int failure = -1;
    
        if(!(ePortier = AllocPortier()))
        {
            // goto Failure;
        	failure = 0;
        }
        else
        {
            ePortier->myPort = PORTIER_PORT;
            ePortier->OnRecv = MsgFunc;
            ePortier->mySock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if(ePortier->mySock == INVALID_SOCKET)
            {
            	// goto Failure2;
               failure = 1;
    		}
    		else
    		{
            	ePortier->OnRecv = MsgFunc;
            	if(!BindPortier(PORTIER_PORT))
            	{
                	// goto Failure3;
                	failure = 2;
                }
    		}
    	}
    	switch( failure )
    	{
    		case -1:
    			...
    			break;
    		case ...
    	}
    }
    


  • Gotthard3 schrieb:

    Mir fällt da nur die Möglichkeit mit dem goto ein, um in umgekehrter Reihenfolge die Resourcen im Misserfolgsfall freizugeben.

    Es gibt (seltene) Situationen, wo Fehlerbehandlung mit goto sinnvoll ist. In deinem Falle ist if/else jedoch eindeutig besser:

    hHost CreatePortier(HostPhone MsgFunc){
        if(ePortier != NULL || !MsgFunc)
            return NULL;
    
        if(!DLInit){
            if(!wsDLInit())
                return NULL;
            DLInit = 1;
        }
    
        if(ePortier = AllocPortier())
        {
          ePortier->myPort = PORTIER_PORT;
          ePortier->OnRecv = MsgFunc;
          ePortier->mySock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
          if(ePortier->mySock != INVALID_SOCKET)
          {
            ePortier->OnRecv = MsgFunc;
            if(BindPortier(PORTIER_PORT))
              return ePortier;
    
            closesocket(ePortier->mySock);
          }
          DeAllocPortier();
        }
        if(DLInit) DLUnInit();
        ePortier = NULL;
    
        return NULL;
    }
    


  • Die Verwendung von goto ist nicht verboten, birgt viele Gefahren und ist deshalb zu Recht verpönt. Meist kann man ein goto leicht vermeiden und man braucht es deswegen nicht zwingend. Manchmal macht ein goto aber dennoch Sinn, z.B. zum Ausstieg aus einer tief verschachtelten Iteration an das Ende einer komplexen Funktion. Also goto nur dann einsetzen wenn while etc ... unübersichtlich werden und dann besser nur ans Ende der Funktion.



  • [quote="Konfusius"]

    Gotthard3 schrieb:

    In deinem Falle ist if/else jedoch eindeutig besser

    Wenn man deinen Code gescheit formatiert (mit indent -linux` '), sieht er so aus:

    hHost CreatePortier(HostPhone MsgFunc)
    {
            if (ePortier != NULL || !MsgFunc)
                    return NULL;
    
            if (!DLInit) {
                    if (!wsDLInit())
                            return NULL;
                    DLInit = 1;
            }
    
            if (ePortier = AllocPortier()) {
                    ePortier->myPort = PORTIER_PORT;
                    ePortier->OnRecv = MsgFunc;
                    ePortier->mySock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
                    if (ePortier->mySock != INVALID_SOCKET) {
                            ePortier->OnRecv = MsgFunc;
                            if (BindPortier(PORTIER_PORT))
                                    return ePortier;
    
                            closesocket(ePortier->mySock);
                    }
                    DeAllocPortier();
            }
    
            if (DLInit) // Dieses if ist unnötig
                    DLUnInit();
    
            ePortier = NULL;
            return NULL;
    }
    

    Ich finde das schlechter als mit goto, weil es eine Weile dauert, bis ich verstanden habe, dass alles nach return ePortier; Fehlerbehandlung ist und eigentlich gar nicht ausgeführt wird. Folgendes zeigt doch eindeutig, dass das ein Programmablauf ohne Verzweigungen ist und macht klar, was eigentlich ausgeführt werden sollte und was nicht:

    hHost CreatePortier(HostPhone MsgFunc)
    {
            if (ePortier != NULL || !MsgFunc)
                    return NULL;
    
            if (!DLInit) {
                    if (!wsDLInit())
                            return NULL;
                    DLInit = 1;
            }
            if (!(ePortier = AllocPortier()))
                    goto got_dl;
            ePortier->myPort = PORTIER_PORT;
            ePortier->OnRecv = MsgFunc;
            ePortier->mySock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if (ePortier->mySock == INVALID_SOCKET)
                    goto got_portier;
            ePortier->OnRecv = MsgFunc;
            if (!BindPortier(PORTIER_PORT))
                    goto got_socket;
            return ePortier;
    
     got_socket:
            closesocket(ePortier->mySock);
     got_portier:
            DeAllocPortier();
     got_dl:
            DLUnInit();
            ePortier = NULL;
            return NULL;
    }
    

    Für Fehlerbehandlung empfinde ich goto als das perfekte Werkzeug, hier soll es konsequent angewendet werden (selbst wenn nur ein einziges if nötig wäre), ansonsten nur noch um ein Mehrfachbreak nachzubauen.
    Tricks wie bool-Flags finde ich total unnötig, weil umständlich in der Umsetzung/Nachvollziehbarkeit und langsamer als goto s.


Anmelden zum Antworten