goto in solchen Fällen unvermeidlich?
-
Gotthard3 schrieb:
Nicht selten wird man darauf hingewiesen, dass man kein goto verwenden darf,
weil es 'schlechter Stil' sei.Ja, im Allgemeinen. Wenn es doch mal passt, gilt das natürlich nicht.
-
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 alsgoto
s.