goto und die Vorurteile

Schnelle objektorientierte, kompilierende Programmiersprache.
Benutzeravatar
fat-lobyte
Beiträge: 1398
Registriert: Sa Jul 05, 2008 12:23 pm
Wohnort: ::1
Kontaktdaten:

goto und die Vorurteile

Beitrag von fat-lobyte » Mi Jan 28, 2009 12:16 pm

Hallo!
In einem anderen Thread ist wiedermal aufgekommen dass goto von grund auf böse und nicht zu verwenden ist. Ich weiß nicht ob das ernst gemeint war, oder ob das nur dazu diente einem Anfänger das goto abzugewöhnen.

Auf jeden Fall stelle ich die Behauptung auf, dass es in C zwei Anwendungsfälle, und in C++ einen Anwendungsfall gibt, wo goto unentbehrlich ist.

Der erste Anwendungsfall ist ziemlich eindeutig: herausspringen aus verschachtelten schleifen.
Das gilt für C gleichermaßen wie für C++.

Code: Alles auswählen

#include <stdio.h>

int main()
{
    int uninitialized[90][60][90];
    int i, j, k;

    for (i = 0; i < 90; i++)
        for (j = 0; j < 60; i++)
            for (k = 0; k < 90; i++)
            {
               if (uninitialized[i][j][k] == 1337)
                {
	                printf("That's too l33t for me! I have to get out!\n");
	                goto rescue_point;
                }
            }

    printf ("Everything went fine.\n");

rescue_point:
    printf("We are safe again.\n");

    return 0;
}

Der zweite Anwendungsfall ist ein wenig komplizierter:
Fordert eine Funktion mehrere Ressourcen an, von denen jede Anforderung fehlschlagen kann, und gibt es auch danach Möglichkeiten für Fehler in denen die Funktion abbrechen muss, so ist es schwierig oder zumindest sehr umständlich an jedem Punkt an dem die Funktion abbrechen kann die Ressourcen wieder freizugeben.
Ein Goto erleichtert hier vieles. Hier mal ein "etwas überspitzter" code, der allerdings die problematik ungefähr darstellen soll:

Code: Alles auswählen

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

const int CATASTROPHAL_ERROR = 0x666;
const int ALOTOFSPACE = 0x100;

#define DO_STUFF() 

int functionThatCanFail()
{
    return rand() % 2;
}

static int resourceFunctionWorking;

void functionWithALotOfResources()
{
    char* uselessbuffer = NULL;
    char* sometext = NULL;
    FILE* bigfile = NULL;

    resourceFunctionWorking = 1;

    uselessbuffer = malloc(ALOTOFSPACE * sizeof(char));
    if (uselessbuffer == NULL)
    {
            printf("Failed to allocate space. Bailing out.\n");
            return;
    }

    sometext = malloc(ALOTOFSPACE * sizeof(char));
    if (sometext == NULL)
    {
            printf("Failed to allocate space. Bailing out.\n");
            return;
    }

    if (!functionThatCanFail())
    {
            printf("Function failed. Bailing out.\n");
            return;
    }
    
    bigfile = fopen("filethatdoesntexist.txt", "r");
    if (bigfile == NULL)
    {
            perror("Failed to open file");
            return;
    }

    DO_STUFF();

    free(uselessbuffer);
    free(sometext);
    fclose(bigfile);
    resourceFunctionWorking = 0;
}

int main()
{
    srand(time(NULL));

    functionWithALotOfResources();

    if (resourceFunctionWorking)
    {
        printf("Help! I'm confused! What went wrong? Bailing out...\n");
        exit(CATASTROPHAL_ERROR);
    }
    
    return 0;
}
Schlägt in der Funktion functionWithALotOfResources() irgendetwas Fehl, so gibt es mit ziemlicher sicherheit Speicherlecks und auch möglicherweise ein free(NULL). Manche Programme arbeiten auch mit globalen variablen, die einen bestimmten zustand haben müssen, das wurde mit der Variable "resourceFunctionWorking" skizziert.
Die Lösung ist entweder: in jedem einzelnen if (fehlschlag){} block alle ressourcen zu überprüfen, und sie dann freizugeben ODER ein kleines goto. Ich finde diese Möglichkeit ist um einiges Eleganter. Der Code der veränderten functionWithALotOfResources() funktion sieht so aus:

Code: Alles auswählen

void functionWithALotOfResources()
{
    char* uselessbuffer = NULL;
    char* sometext = NULL;
    FILE* bigfile = NULL;

    resourceFunctionWorking = 1;

    uselessbuffer = malloc(ALOTOFSPACE * sizeof(char));
    if (uselessbuffer == NULL)
    {
            printf("Failed to allocate space. Bailing out.\n");
            goto exit_point;
    }

    sometext = malloc(ALOTOFSPACE * sizeof(char));
    if (sometext == NULL)
    {
            printf("Failed to allocate space. Bailing out.\n");
            goto exit_point;
    }

    if (!functionThatCanFail())
    {
            printf("Function failed. Bailing out.\n");
            goto exit_point;
    }
    
    bigfile = fopen("filethatdoesntexist.txt", "r");
    if (bigfile == NULL)
    {
            perror("Failed to open file");
            goto exit_point;
    }

    DO_STUFF();

exit_point:
    if (uselessbuffer)
        free(uselessbuffer);

    if (uselessbuffer)
        free(sometext);
    
    if(bigfile)
        fclose(bigfile);

    resourceFunctionWorking = 0;
}
Dieses Goto ist nur in C notwendig, da man sich in C++ mit Exceptions aushelfen kann.


Ich bin gespannt auf Gegenargumente. Ich finde allerdings man sollte eine MÖGLICHKEIT einer Sprache nicht von vornherein verteufeln, sondern sich darüber im klaren sein, dass es immer wege gibt in der man sie einsetzen kann.
Um diese Nachricht zu verbreiten hätte ich gerne einen Eintrag in der FAQ, mit dem Titel "goto und wie man es verhindern kann", das zwar auf die Gefahren und Probleme des "klassischen" gebrauchs von goto eingeht, allerdings eben auch auf die Probleme die ohne es nicht lösbar sind.

Zum Abschluss möchte ich noch einen kleinen Comic von Randell Munroe, aus seiner wunderbaren Seite xkcd.com einfügen, der ein bisschen die Furcht vor dem goto parodiert:

Bild



request for comments

mfg, fat-lobyte
Haters gonna hate, potatoes gonna potate.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8861
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: goto und die Vorurteile

Beitrag von Xin » Mi Jan 28, 2009 1:24 pm

Bei Möglichkeit 1 stimme ich zu.

Bei Möglichkeit 2 lassen sich die If-Abfragen verschachteln, so dass ein nicht redundanter Abbau von Resourcen stattfindet, der zusätzlich nicht prüfen muss, ob eine Resource überhaupt belegt wurde.

Wem Verschachtelung von if-Abfragen optisch nicht gefällt, sollte sich fragen, ob er ein Problem aufgrund des Problems oder der Optik des Problems lösen möchte.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

nufan
Wiki-Moderator
Beiträge: 2558
Registriert: Sa Jul 05, 2008 3:21 pm

Re: goto und die Vorurteile

Beitrag von nufan » Mi Jan 28, 2009 2:45 pm

Naja, zum ersten Problem gibts noch die return-Alternative.

Code: Alles auswählen

#include <stdio.h>

int checkarray (int array[]);


int main()
{

  int uninitialized[90][60][90], rescuepoint = 0;
        
  rescuepoint = checkarray (uninitialized);      
    
  if (!rescuepoint)      
    printf ("Everything went fine.\n");

    else
      printf("We are safe again.\n");

  return 0;

}


int checkarray (int array [])
{

  int i, j, k;

  for (i = 0; i < 90; i++)
      for (j = 0; j < 60; j++)    // hätte fast den gleichen copy&paste Fehler gemacht ^^ 
          for (k = 0; k < 90; k++) 
          {
             if (array[i][j][k] == 1337)
             {
                 printf("That's too l33t for me! I have to get out!\n");
                 return 1;
             }
          }

  return 0;

}
Kann aber bei komplizierten Schleifen sehr aufwendig werden...

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8861
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: goto und die Vorurteile

Beitrag von Xin » Mi Jan 28, 2009 2:58 pm

dani93 hat geschrieben:Naja, zum ersten Problem gibts noch die return-Alternative.
Diese ist mir auch erst hier im Forum bewußt geworden. Die Idee ist wirklich schön, allerdings ist sie - wenn man mit Performancegründen argumentiert - dem goto trotzdem nicht vorzuziehen, da der Funktionsaufruf ebenfalls Zeit kostet, während goto sehr preiswert ist.

Das Manko an Goto ist ja auch nicht der Sprung - den führt return, break oder continue ebenso aus - sondern die freie Definierbarkeit des Zielposition. Für hochperformante Algorithmen kann das nützlich sein. Für den Alltag oder gar das Lernen von Programmierung ist das allerdings eher eine Katastrophe.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
cloidnerux
Moderator
Beiträge: 3123
Registriert: Fr Sep 26, 2008 4:37 pm
Wohnort: Ram (Gibts wirklich)

Re: goto und die Vorurteile

Beitrag von cloidnerux » Mi Jan 28, 2009 4:19 pm

Grundsätzlich solte goto seinen Platz in der Programmierung behalten, dennoch sollte man doch aus gründen der Übersichtlichkeit und vermeidung von BUGs und Fehlern auf gotos verzichten, da man beim Programmieren nicht mehr weiß, was hinter der Sprungmarke 2000 Zeilen weiter geschiet.
Außerdem sollte man vorallem Anfänger davor beschützen an jeder zweiten Stelle im Programm gotos zu verwenden.
Redundanz macht wiederholen unnötig.
quod erat expectandum

Benutzeravatar
fat-lobyte
Beiträge: 1398
Registriert: Sa Jul 05, 2008 12:23 pm
Wohnort: ::1
Kontaktdaten:

Re: goto und die Vorurteile

Beitrag von fat-lobyte » Mi Jan 28, 2009 6:05 pm

Xin hat geschrieben:Bei Möglichkeit 2 lassen sich die If-Abfragen verschachteln, so dass ein nicht redundanter Abbau von Resourcen stattfindet, der zusätzlich nicht prüfen muss, ob eine Resource überhaupt belegt wurde.
Wie würde das aussehen, ich kann mir das im moment schwer vorstellen. Wird das nicht ziemlich anstrengend, und ähnlich fehleranfällig wie goto, wenn der code größer ist und dann so extrem verschachtelt ist?
Haters gonna hate, potatoes gonna potate.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8861
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: goto und die Vorurteile

Beitrag von Xin » Mi Jan 28, 2009 8:59 pm

fat-lobyte hat geschrieben:
Xin hat geschrieben:Bei Möglichkeit 2 lassen sich die If-Abfragen verschachteln, so dass ein nicht redundanter Abbau von Resourcen stattfindet, der zusätzlich nicht prüfen muss, ob eine Resource überhaupt belegt wurde.
Wie würde das aussehen, ich kann mir das im moment schwer vorstellen. Wird das nicht ziemlich anstrengend, und ähnlich fehleranfällig wie goto, wenn der code größer ist und dann so extrem verschachtelt ist?
Mal schnell zurechtkopiert:

Code: Alles auswählen

void functionWithALotOfResources()
{
  char* uselessbuffer = NULL;
  char* sometext = NULL;
  FILE* bigfile = NULL;
  resourceFunctionWorking = 1;

  uselessbuffer = malloc(ALOTOFSPACE * sizeof(char));
  if ( uselessbuffer )
  {
    sometext = malloc(ALOTOFSPACE * sizeof(char));

    if(sometext)
    {
      if (functionThatCanFail())
      {
        bigfile = fopen("filethatdoesntexist.txt", "r");

        if( bigfile )
        {
          DO_STUFF();

          fclose(bigfile);
        }
        else
          perror("Failed to open file");
      }
      else
        printf("Function failed. Bailing out.\n");

      free(sometext);
    }
    else
      printf("Failed to allocate space. Bailing out.\n");

    free(uselessbuffer);
  }
  else
    printf("Failed to allocate space. Bailing out.\n");
}
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
fat-lobyte
Beiträge: 1398
Registriert: Sa Jul 05, 2008 12:23 pm
Wohnort: ::1
Kontaktdaten:

Re: goto und die Vorurteile

Beitrag von fat-lobyte » Mi Jan 28, 2009 11:13 pm

Xin hat geschrieben:
fat-lobyte hat geschrieben:
Xin hat geschrieben:Bei Möglichkeit 2 lassen sich die If-Abfragen verschachteln, so dass ein nicht redundanter Abbau von Resourcen stattfindet, der zusätzlich nicht prüfen muss, ob eine Resource überhaupt belegt wurde.
Wie würde das aussehen, ich kann mir das im moment schwer vorstellen. Wird das nicht ziemlich anstrengend, und ähnlich fehleranfällig wie goto, wenn der code größer ist und dann so extrem verschachtelt ist?
Mal schnell zurechtkopiert:

Code: Alles auswählen

void functionWithALotOfResources()
{
  char* uselessbuffer = NULL;
  char* sometext = NULL;
  FILE* bigfile = NULL;
  resourceFunctionWorking = 1;

  uselessbuffer = malloc(ALOTOFSPACE * sizeof(char));
  if ( uselessbuffer )
  {
    sometext = malloc(ALOTOFSPACE * sizeof(char));

    if(sometext)
    {
      if (functionThatCanFail())
      {
        bigfile = fopen("filethatdoesntexist.txt", "r");

        if( bigfile )
        {
          DO_STUFF();

          fclose(bigfile);
        }
        else
          perror("Failed to open file");
      }
      else
        printf("Function failed. Bailing out.\n");

      free(sometext);
    }
    else
      printf("Failed to allocate space. Bailing out.\n");

    free(uselessbuffer);
  }
  else
    printf("Failed to allocate space. Bailing out.\n");
}
Na gut, das ist auch eine Möglichkeit. Ich finde diese ist allerdings weder übersichtlicher noch sicherer als die Goto variante. Ich sehe hier keine Vorteile außer, dass das wort goto nicht vorkommt.
Haters gonna hate, potatoes gonna potate.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8861
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: goto und die Vorurteile

Beitrag von Xin » Mi Jan 28, 2009 11:19 pm

fat-lobyte hat geschrieben:Na gut, das ist auch eine Möglichkeit. Ich finde diese ist allerdings weder übersichtlicher noch sicherer als die Goto variante. Ich sehe hier keine Vorteile außer, dass das wort goto nicht vorkommt.
Zum einen ist es übersichtlicher, weil der Editor die Klammern zuordnen kann und man entsprechend sieht, wo der else-Zweig ist.
Zum anderen ist es besser, weil nie der Versuch gestartet wird, eine Resource freizugeben, die nie belegt wurde. Es werden genauso Resourcen rückwärts abgebaut, wie sie zuvor aufgebaut wurden.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
Dirty Oerti
Beiträge: 2229
Registriert: Di Jul 08, 2008 5:05 pm
Wohnort: Thurndorf / Würzburg

Re: goto und die Vorurteile

Beitrag von Dirty Oerti » Mi Jan 28, 2009 11:22 pm

Zu goto:

Wer goto aus Performancegründen wählt, muss schon sehr gute Argumente haben.
Der Aufruf einer Funktion dauert auf heutigen CPUs (und auch auf nicht ganz uralten) KAUM oder teilweise nahe GAR nicht länger als ein einfacher Sprung.

Ich kann mir also nicht vorstellen, dass das etwas bringt. Außer, es steht in einer Schleife, die 20 Mrd mal durchlaufen werden muss. Selbst dann wäre es wahrscheinlich klüger, einfach eine inline-Funktion zu benutzen...
Bei Fragen einfach an daniel[ät]proggen[Punkt]org
Ich helfe gerne! :)
----------
Wenn du ein Licht am Ende des Tunnels siehst, freu dich nicht zu früh! Es könnte ein Zug sein, der auf dich zukommt!
----
It said: "Install Win95 or better ..." So I installed Linux.

Antworten