Ist goto ausnahmslos immer schlecht?



  • Hallo, ich schreib g'rad ein kleines Programm, das sieht zur Zeit so aus:

    int main(){
    
    	STDSTRING fileNameInput = {'\0'}; /* wird zum Einlesen von Tastatureingaben benutzt */
    	FILE* workFile = NULL;
    
    	printf("Import-Datei-Name: \n\t");
    	stdInput(fileNameInput, STDSTRINGSIZE);
    	if(!(workFile = fopen(fileNameInput, "r"))){
    		printf("Datei konnte nicht geoeffnet werden. ");
    		goto Programmende; /* GOTO ? */
    	}
    /* ... */
    
    Programmende:
    	fgetc(stdin); /* verhindert vorzeitige Schliessung des Konsolenfensters */
    	return 0;
    }
    

    Wenn dabei im Laufe des Programms ein Fehler (Datei nicht gefunden usw.) soll es beendet werden, und dabei sollte vor dem return 0 immer noch ein fgetc(stdin) stehen, damit das Konsolenfenster nicht vorzeitig geschlossen wird.
    Anstatt dass ich jetzt bei jeder möglichen Stelle, wo ein Fehler auftreten könnte, die beiden Zeilen fgetc(stdin); return 0; hinschreibe, habe ich mir gedacht, es ist eleganter, wenn ich eine Sprungmarke am Ende von main verwende.
    Sonst vergesse ich womöglich einmal irgendwo, das fgetc(stdin) dazu zu schreiben, wenn ein Fehler auftritt.

    Jetzt hat ja goto eigentlich keinen guten Ruf (warum eigentlich?), aber ist das in diesem Fall ausnahmsweise ein sinnvoller Einsatz von 'goto', oder wie sonst könnte man das ohne 'goto' geschickter lösen (Es ist noch nicht vorherzusehen, wo überall beim Programmlauf Fehler auftreten könnten!)?

    Ist das eine sehr strenge Regel, dass man kein goto verwenden darf, oder kann man da öftere Male eine Ausnahme von der Regel machen?



  • Grundler schrieb:

    Jetzt hat ja goto eigentlich keinen guten Ruf (warum eigentlich?), ...

    Edsger W. Dijkstra:

    http://www.u.arizona.edu/~rubinson/copyright_violations/Go_To_Considered_Harmful.html

    http://david.tribble.com/text/goto.html

    ---

    Wenn Du einmal versuchst hast 60 Zeilen Spaghetti-Code mit 20 gotos zu verstehen, weisst Du warum.



  • Grundler schrieb:

    Jetzt hat ja goto eigentlich keinen guten Ruf (warum eigentlich?),

    dass 'goto' generell schlecht ist, ist nur ein dummes ammenmärchen.

    Grundler schrieb:

    aber ist das in diesem Fall ausnahmsweise ein sinnvoller Einsatz von 'goto', oder wie sonst könnte man das ohne 'goto' geschickter lösen

    mit so'ner funktion z.b.

    void goodbye(void)
    {
      getchar();
      exit(0);
    }
    

    Grundler schrieb:

    Ist das eine sehr strenge Regel, dass man kein goto verwenden darf, oder kann man da öftere Male eine Ausnahme von der Regel machen?

    zu viele kreuz-und-quer sprünge in einer funktion können die lesbarkeit erschweren. ein geschickt platziertes goto kann den code sogar lesbarer machen. also, keine angst vor gotos. wenn's gut passt, immer rein damit.
    🙂



  • +fricky schrieb:

    zu viele kreuz-und-quer sprünge in einer funktion können die lesbarkeit erschweren. ein geschickt platziertes goto kann den code sogar lesbarer machen. also, keine angst vor gotos. wenn's gut passt, immer rein damit.
    🙂

    Mit diesem Satz negierst Du bescheidene 40 Jahre strukutierte Programmierung.



  • hartmut1164 schrieb:

    Mit diesem Satz negierst Du bescheidene 40 Jahre strukutierte Programmierung.

    darauf kann ich nur damit antworten: http://pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf
    🙂



  • Wird 'goto' nicht etwas unterschätzt (?)
    Man könnte doch damit mit einfachen Mitteln eine Art Ausnahmen- bzw. Fehlerbehandlung nachbasteln.

    Beispielcode:

    int func(int x, int y){
    /* fuer alle moeglichen Fehler, die waehrend der Ausfuehrung der Funktion
       auftreten koennten, definiert man eine Konstante */
    const int moeglicherFehler_1 = 1,
     moeglicherFehler_2 = 2, moeglicherFehler_n = n;
    int fehlerFrei = 0;
    
    /* Anweisungen, und falls irgendwo ein Fehler auftritt, setzt man die
       Variable fehlerFrei auf den entsprechenden Wert des Fehlercodes und verzweigt mit goto
       zum Ende der Funktion (z.B. Sprungmarke 'Funktionsende:' */
    
    Funktionsende:
    switch(fehlerFrei){
     case 0: /* wir sind bis hierher gelangt, weil kein Fehler aufgetreten ist */
      return resultat;
     case 1: /* in jedem anderen Fall ist ein Fehler aufgetreten */
      /* Fehlerbehandlung für Fehler des 1. Typs ... */
     case n:
      /* Fehlerbehandlung für Fehler des n-ten Typs */
    }
    }
    

    Das ist doch eine Vorgangsweise, die einem try ... catch Konstrukt ziemlich ähnlich ist (?)
    So trennt man eine Funktion in zwei übersichtliche Teile:
    Den tatsächlichen Programmablauf (dem dadurch leichter zu folgen ist),
    und einem Unterbereich, der sich mit der Fehlerbehandlung beschäftigt.
    Nichts macht ein Stück Quellcode unübersichtlicher als wenn auf jede Anweisung, die einen Fehler verursachen könnte, sofort ein Block mit Fehlerbehandlung folgt, bevor es mit dem eigentlichen Programmablauf wieder weitergeht. Mit dem goto-Konstrukt ließe sich der Fehlerbehandlungsteil relativ elegant vom eigentlich Programmablauf trennen.

    Vielleicht genießt 'goto' seinen schlechten Ruf doch zu Unrecht (?)



  • hartmut1164 schrieb:

    +fricky schrieb:

    zu viele kreuz-und-quer sprünge in einer funktion können die lesbarkeit erschweren. ein geschickt platziertes goto kann den code sogar lesbarer machen. also, keine angst vor gotos. wenn's gut passt, immer rein damit.
    🙂

    Mit diesem Satz negierst Du bescheidene 40 Jahre strukutierte Programmierung.

    Welche Version findest du besser?

    A) ohne goto

    void funcA(size_t size)
    {
    	char *buffer = malloc(size);
    
    	int some_res = get_sys_res(SOME_CONST);
    
    	FILE *file = fopen("/the_life/the_universe/and/everything", "r");
    
    	int err = do_something_with(some_res, buffer);
    	if (err != 0) {
    		free(buffer);
    		fclose(file);
    		free_sys_res(some_res);
    		return;
    	}
    
    	err = do_something_else(file, some_res);
    	if (err != 0) {
    		free(buffer);
    		fclose(file);
    		free_sys_res(some_res);
    		return;
    	}
    
    	if (!everything_ok(buffer, file)) {
    		free(buffer);
    		fclose(file);
    		free_sys_res(some_res);
    		return;
    	}
    
    	and_another_func(buffer, file, some_res);
    
    	free(buffer);
    	fclose(file);
    	free_sys_res(some_res);
    	return;
    }
    

    oder 😎 mit goto:

    void funcB(size_t size)
    {
    	char *buffer = malloc(size);
    
    	int some_res = get_sys_res(SOME_CONST);
    
    	FILE *file = fopen("/the_life/the_universe/and/everything", "r");
    
    	int err = do_something_with(some_res, buffer);
    	if (err != 0) {
    		goto cleanup;
    	}
    
    	err = do_something_else(file, some_res);
    	if (err != 0) {
    		goto cleanup;
    	}
    
    	if (!everything_ok(buffer, file)) {
    		goto cleanup;
    	}
    
    	and_another_func(buffer, file, some_res);
    
    cleanup:	
    	free(buffer);
    	fclose(file);
    	free_sys_res(some_res);
    }
    

    Und jetzt stell dir mal vor, du musst in der Funktion auf einmal noch eine Datei öffnen, mehr Speicher allozieren oder sonst irgendwas machen, was hinterher aufgeräumt werden muss. In welcher Version kannst du einfacher und damit Fehlerresistenter den Aufräumbefehl einfügen?



  • +fricky schrieb:

    hartmut1164 schrieb:

    Mit diesem Satz negierst Du bescheidene 40 Jahre strukutierte Programmierung.

    darauf kann ich nur damit antworten: http://pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf
    🙂

    Ich halte sogar das break- und continue-Statement in einem sauberen Programm bei Loops fuer nicht zu laessig, weil es eben ein verstecktes goto ist (das gilt fuer den Missbrauch bei case-switch-Strukturen). Das gilt (bei C++) in einem grossen Masse auch fuer Exceptions.

    Wenn man Programme von Anfang an in klaren Diagrammen (Nasi-Schneiderman oder aehnlichen) strukturiert, dann kommt man garnicht auf die Idee, soetwas zu verwenden (braucht noch nicht mal auf dem Papier zu erfolgen, sondern im Kopf).

    ---

    NB: D. Knuth wurde damals ziemlich schnell von N. Wirth, von mindestens aehnlicher Reputation, widersprochen.



  • DerKuchen schrieb:

    hartmut1164 schrieb:

    +fricky schrieb:

    zu viele kreuz-und-quer sprünge in einer funktion können die lesbarkeit erschweren. ein geschickt platziertes goto kann den code sogar lesbarer machen. also, keine angst vor gotos. wenn's gut passt, immer rein damit.
    🙂

    Mit diesem Satz negierst Du bescheidene 40 Jahre strukutierte Programmierung.

    Welche Version findest du besser?

    A) ohne goto

    Und jetzt stell dir mal vor, du musst in der Funktion auf einmal noch eine Datei öffnen, mehr Speicher allozieren oder sonst irgendwas machen, was hinterher aufgeräumt werden muss. In welcher Version kannst du einfacher und damit Fehlerresistenter den Aufräumbefehl einfügen?

    z. B. so:

    void funcA(size_t size)
    {
        char     *buffer   = NULL;
        int       some_res = 0;
        FILE     *file     = NULL;
        int       err      = 0;
    
        buffer = malloc(sizeof (char) * size);
        if (buffer == NULL)
        {
            fprintf (stderr, "Can't alloc mem\n");
            err = 1;
        }
        else
        {
            some_res = get_sys_res(SOME_CONST);
            file = fopen("/the_life/the_universe/and/everything", "r");
            if (file == NULL)
            {
                 fprintf (stderr, "Can't open file - details: Code <%d> Mssg: <%s>\n", 
                            errno, strerror (errno)");
                 err = 1;
            }
        }
    
        if ((err                                  != 0) &&
            (do_something_with (some_res, buffer) != 0) &&
            (do_something_else (file, some_res)   != 0) &&
            (everything_ok     (buffer, file)     != 0))
                   and_another_func(buffer, file, some_res);
    
        if (buffer != NULL) free   (buffer);
        if (file   != NULL) fclose (file  );
        free_sys_res(some_res);
    
        return;
    }
    

    Edit - einwenig vereinfacht



  • Also, ich finde die Version mit den gotos deutlich lesebarer und übersichtlicher. Und die Linux-Kernel Hacker ebenso, sie verwenden gotos vor allem bei Initialisieren von Modulen, da lernt man schon goto schätzen. Merke, goto macht keinen Code zum Spaghetti Code, sondern dessen Missbrauch.



  • hartmut1164 schrieb:

    Ich halte sogar das break- und continue-Statement in einem sauberen Programm bei Loops fuer nicht zu laessig, weil es eben ein verstecktes goto ist

    das 'while' am ende einer do/while schleife ist auch ein verstecktes goto. oder das } am ende eines while- oder for-schleifenblocks, switch/case auch. du solltest die alle meiden, sind absolut böse gotos.

    hartmut1164 schrieb:

    NB: D. Knuth wurde damals ziemlich schnell von N. Wirth, von mindestens aehnlicher Reputation, widersprochen.

    trotzdem hat wirth in sein pascal ein goto-statement eingebaut, der alte witzbold.
    🙂



  • pauschalisierungen sind immer schlecht.

    hartmut1164 schrieb:

    Ich halte sogar das break- und continue-Statement in einem sauberen Programm bei Loops fuer nicht zu laessig, weil es eben ein verstecktes goto ist

    aber wo liegt die grenze zwischen lässig und zu lässig?

    +fricky schrieb:

    das 'while' am ende einer do/while schleife ist auch ein verstecktes goto. oder das } am ende eines while- oder for-schleifenblocks, switch/case auch. du solltest die alle meiden, sind absolut böse gotos.

    und erst der generierte maschinencode! der strotzt nur so von gotos, auch an stellen wo man es gar nicht erwartet!



  • Grundler schrieb:

    Jetzt hat ja goto eigentlich keinen guten Ruf (warum eigentlich?), aber ist das in diesem Fall ausnahmsweise ein sinnvoller Einsatz von 'goto', oder wie sonst könnte man das ohne 'goto' geschickter lösen (Es ist noch nicht vorherzusehen, wo überall beim Programmlauf Fehler auftreten könnten!)?

    Ist das eine sehr strenge Regel, dass man kein goto verwenden darf, oder kann man da öftere Male eine Ausnahme von der Regel machen?

    Lies "Go To Statement Considered Harmful" von Edsger Dijkstra und bilde dir deine eigene Meinung. Die meisten Leute kennen leider nur den Titel und nicht den Inhalt, geschweige denn die Argumentation, und verteufeln goto vollkommen.

    Lies dann noch das Knuth-Paper, das hier verlinkt wurde.

    Und wenn du das alles hast sacken lassen, besinne dich auf die Realität. Im echten Leben hat man meistens Vorgesetzte, die gut Dinge organisieren können (deshalb sind sie Vorgesetzte), aber von solchen philosophischen Details keine Ahnung haben, und dir ein goto negativ ankreiden werden. Der Kampf ist längst gefochten und verloren, goto wird sein Stigma nicht mehr los.



  • +fricky schrieb:

    hartmut1164 schrieb:

    Ich halte sogar das break- und continue-Statement in einem sauberen Programm bei Loops fuer nicht zu laessig, weil es eben ein verstecktes goto ist

    das 'while' am ende einer do/while schleife ist auch ein verstecktes goto. oder das } am ende eines while- oder for-schleifenblocks, switch/case auch. du solltest die alle meiden, sind absolut böse gotos.

    Der Compiler setzt es in gotos um; aber wir reden ueber Hochsprachen-Code und hier sollten klare Verarbeitungs- und Ablaufbloecke gewahrt bleiben. Die Integritaet dieser Bloecke ist eben ein wesentlicher Bestandteil der strukturierten Programmierung.

    +fricky schrieb:

    hartmut1164 schrieb:

    NB: D. Knuth wurde damals ziemlich schnell von N. Wirth, von mindestens aehnlicher Reputation, widersprochen.

    trotzdem hat wirth in sein pascal ein goto-statement eingebaut, der alte witzbold.
    🙂

    Zumindest Modula-2 besitzt es nicht mehr (und glaube Modula-1 ebenfalls nicht).



  • hartmut1164 schrieb:

    +fricky schrieb:

    hartmut1164 schrieb:

    NB: D. Knuth wurde damals ziemlich schnell von N. Wirth, von mindestens aehnlicher Reputation, widersprochen.

    trotzdem hat wirth in sein pascal ein goto-statement eingebaut, der alte witzbold.
    🙂

    Zumindest Modula-2 besitzt es nicht mehr (und glaube Modula-1 ebenfalls nicht).

    und wie kommt man in modula aus, sagen wir mal, einer dreifach verschachtelten schleife raus, wenn sich im innern entscheidet, dass keine durchläufe (auch nicht der äusseren schleifen) mehr nötig sind? oder kann man in modula sowas gar nicht erst programmieren?
    🙂



  • Dass man jedes C-Programm theoretisch auch ohne goto schreiben kann, lasse ich mir einreden. Aber ich kann mir nicht vorstellen, dass das auch für break; oder continue; gilt (es sei denn, man würde Laufvariablen im Schleifenrumpf manipulieren wollen ...). Nicht für jede Schleife mit break; lässt sich eine äquivalente Schleife ohne break; erfinden.
    Zumindest kann ich mir nicht vorstellen, dass das Gegenteil beweisbar wäre.



  • +fricky schrieb:

    [...]sagen wir mal, einer dreifach verschachtelten schleife raus, wenn sich im innern entscheidet, dass keine durchläufe (auch nicht der äusseren schleifen) mehr nötig sind?

    Das ist genau der Fall, wo ich auch gerne mal ein goto einsetze. Zuletzt (wohl auch der einzige Einsatz in Code, der zum Kunden ging) bei einer Hough-Transformation, bei der Schleifen 7- oder 8-fach verschachtelt waren. Warum sollte man in jede Schleife eine zusätzliche Abbsuchbedingung packen...



  • sprunghaft schrieb:

    Dass man jedes C-Programm theoretisch auch ohne goto schreiben kann, lasse ich mir einreden. Aber ich kann mir nicht vorstellen, dass das auch für break; oder continue; gilt (es sei denn, man würde Laufvariablen im Schleifenrumpf manipulieren wollen ...). Nicht für jede Schleife mit break; lässt sich eine äquivalente Schleife ohne break; erfinden.
    Zumindest kann ich mir nicht vorstellen, dass das Gegenteil beweisbar wäre.

    scherzkeks. erstens ist return eh fast immer besser als break. und zweitens ist das verbot des rumfummelns an der laufbedingung der übliche weg, um aus dogmatischen erwägungen break zu umgehen. gerne mit while(i<anz && !fertig)



  • sprunghaft schrieb:

    Nicht für jede Schleife mit break; lässt sich eine äquivalente Schleife ohne break; erfinden.
    Zumindest kann ich mir nicht vorstellen, dass das Gegenteil beweisbar wäre.

    warum nicht? musst einfach nur in der schleife die abbruchbedingung gewaltsam herbeiführen, beispiel:

    for (s=0; s<100; s++)
      {
        if (s == 10)
          s = 100;   // selbe wirkung wie 'break'
      }
    

    🙂



  • +fricky schrieb:

    sprunghaft schrieb:

    Nicht für jede Schleife mit break; lässt sich eine äquivalente Schleife ohne break; erfinden.
    Zumindest kann ich mir nicht vorstellen, dass das Gegenteil beweisbar wäre.

    warum nicht? musst einfach nur in der schleife die abbruchbedingung gewaltsam herbeiführen, beispiel:

    for (s=0; s<100; s++)
      {
        if (s == 10)
          s = 100;   // selbe wirkung wie 'break'
      }
    

    🙂

    ... und trotzdem muesste nach s = 100 noch continue; stehen. Es könnten nach der if(s == 10) Anweisung ja ohne weiteres noch zusätzliche Anweisungen in der Schleife stehen, von denen man plötzlich auf keinen Fall mehr möchte, dass sie ausgeführt werden.
    Also nicht jede Schleife kann auch ohne break; bzw. continue; auskommen.
    mfg


Anmelden zum Antworten