Goto

Die Anweisung goto ermöglicht in einem Programm an eine bestimmte Stelle zu springen. Allerdings ist sie bei den meisten Programmierern sehr verhasst. Eine falsche Nutzung dieses Befehls kann schnell zu sogenanntem Spaghetti-Code führen oder noch schlimmere Folgen haben. goto kann in bestimmten Fällen aber auch Vorteile haben. In diesem Kapitel werden die Gefahren und Möglichkeiten von goto beschrieben. Vorrangig dient der Artikel jedoch dazu, vorhandene Quelltexte zu verstehen und dann sinnvoll zu überarbeiten.

Beispiel

Damit das Programm weiß, an welche Stelle es springen soll, muss eine Sprungmarke, auch Label genannt, gesetzt werden. Hierfür nimmt man einen Namen für das Label und setzt einen Doppelpunkt dahinter. Diese Sprungmarken kann man anschließend als Parameter für goto verwenden.

Hier ein Beispiel:

#include <stdio.h>
 
int main ()
{
  goto jump;
 
  printf("Dieser Text wird nie ausgegeben\n");  
 
jump:
  return 0;
}

Gefahren

In Assembler ist es üblich, Schleifen und manchmal sogar auch Funktionsaufrufe durch goto zu ersetzen. Diese Praxis hat man teils nach C übertragen. Es wurde nicht mit Labels gespart und selbst wenn ein Programmierer Funktionen nutzte, kam es vor, dass dort auch Labels gesetzt wurden und das Programm zwischen verschiedenen Funktionen springen konnte.
Das führte dazu, dass Quelltexte irgendwann selbst für den Programmierer nicht nachvollziehbar waren. Auch heute kommt es noch vor, dass vor allem Anfänger, die z.B. Probleme mit Schleifen haben, derartige Methoden anwenden. Das ist definitiv ein schlechter, wenn nicht sogar der schlechteste Programmierstil.

Schleifen haben den Vorteil, dass alle Anweisungen in einem Block und die Ausdrücke im Kopf, bzw. Fuß zusammengefasst sind. Das erhöht die Übersicht. So etwas fehlt in einer goto-Schleife. Werden viele Sprungmarken eingesetzt, gibt es zum einen Probleme diese zu finden. Es ist einfach nur zu aufwändig, einen langen Code, nach einem bestimmten Label abzusuchen und sobald man sich mit einem Label vertan hat, sind Fehler nur noch sehr aufwendig zu finden. Kommt kurz nach einem Label eine weitere Sprunganweisung, ist der Verlauf eines Programms schwer nachzuvollziehen. Schlecht benannte Labels sorgen ebenfalls dafür, dass man den Verlauf des Programms nur schwer nachvollziehen kann. Möchte man einen solchen Quelltext nach einer längeren Zeit nochmals überarbeiten, so ist es effektiver, diesen neu zu schreiben.

Hinweise zur Anwendung

Beachtet man folgende Hinweise, so kann man goto in vorhandenen Quelltexten erstmal akzeptieren.

  • Sinnvolle Namensgebung der Labels
    Die Namen sollten den Zweck der Labels beschreiben.
  • Sparsame Nutzung der Labels
    Es ist akzeptabel, nur ein Label in einer Funktion zu benutzen, um die Funktion an genau einer Position zu verlassen und an diese zu springen, um die Funktion statt mit return zu verlassen.
  • Verzicht auf goto-Schleifen
    Schleifen verbessern die Übersicht eines Quelltextes und sind speziell für Wiederholungen gedacht.
  • Sprünge ausschließlich innerhalb ihrer befindlichen Funktion
    Ein Sprung sollte nur innerhalb der Funktion ausgeführt werden, in der sich die goto-Anweisung befindet.
  • Vermeidung von doppelten Sprüngen
    Nach einem Label sollte keine goto-Anweisung mehr stehen.

In C gibt es nur ein wirklich sinnvolles Szenario, um goto zu verwenden: Um aus mehrfach verschachtelten Schleifen herauszuspringen.

void function( void )
{
  for( unsigned int i = 0; i < 10; ++i )
  {
    for( unsigned int j = 0; j < 10; ++j )
    {
      if( i == 5 && j == 5 )
        goto function_for_exit;
    }
  }
 
function_for_exit:
  printf( "Schleife beendet\n" );
}

Das Label ist sinnvoll benannt, der Name beginnt mit dem Funktionsnamen, beschreibt seinen Zweck, ist selbst keine Schleife und springt auch nicht aus der Funktion heraus. Bei Algorithmen, die auf Performance angewiesen sind, hat dies Vorteile, da mit break lediglich die innere Schleife verlassen werden kann. Man müsste für die äußere(n) Schleife(n) also jeweils eine zusätzliche Variable in die Abfrage setzen, um zu entscheiden, ob die Schleife(n) verlassen werden soll(en). Das kostet Zeit und diese spart man sich mit einer goto-Anweisung.

Ein schlechtes Beispiel

Dieses Beispiel zeigt einen Code mit einer verschachtelten goto-Schleife, welcher in zehn Spalten und zehn Zeilen ein Pluszeichen ausgibt.

#include <stdio.h>
 
int main()
{
  int i, j;
  i = 0;
 
_1: /* Start äußere Schleife */
 
  j = 0;
 
_2: /* Start innere Schleife */
 
  printf( "+" );
 
  /* Ende innere Schleife */
  j++;
  if( j < 10 )
    goto _2;
 
  printf( "\n" );
 
  i++;
  if( i < 10 )
    goto _1;
  /* Ende äußere Schleife */
_3:
 
  return 0;
}

Der nächste Quelltext ist mit dem vorherigen Beispielcode gleichzusetzen. Hierbei werden allerdings richtige Schleifen benutzt.

#include <stdio.h>
 
int main()
{
  int i, j;
 
  for( i = 0; i < 10; i++ )     /* Äußere Schleife */
  { 
    for( j = 0; j < 10; j++ )   /* Innere Schleife */
      printf( "+" );
 
    printf( "\n" );
  }
}

Das erste Beispiel zeigt, wie man auf keinen Fall programmieren sollte. Wird solcher Code entdeckt, sollte man sich ruhig die Zeit nehmen und ihn wie gezeigt neu schreiben.

Beizeichnungen wie _1 und _2 für Labels sind schlecht und sorgen dafür, dass man einen Quelltext, der mehrere Sprungmarken enthält, sehr schlecht versteht. Das bedeutet, dass der Inhalt der goto-Schleife auf jeden Fall mindestens einmal ausgeführt wird, auch wenn es unerwünscht ist. Vergleicht man das erste Beispiel mit dem zweiten, stellt man möglicherweise fest, dass im zweiten Beispiel die Schleifen durch die eingerückten Quelltextpassagen und die geschweiften Klammern optisch getrennt sind, wodurch sich diese besser voneinander unterscheiden lassen. Der Quelltext der goto-Schleife ist außerdem wesentlich länger als sein Pendant, was in der Regel auch die Übersicht mindert.

Fazit

Es gibt Programmierer, die goto verfluchen und wirklich nie benutzen. Das ist nicht immer von Vorteil, denn man kann mit goto in einem Quelltext - bei sachgemäßer Nutzung - bestimmte Stellen übersichtlicher gestalten, wie eben mehrfach verschachtelten Schleifen oder dem kontrolliertem Ausstieg aus einer Funktion, so dass ans Ende der Funktion gesprungen wird und die Funktion nicht an beliebigen Stellen verlassen wird.

Es gibt also keinen Grund goto prinzipiell zu verdammen, dennoch sollte der Einsatz von goto überlegt sein und gut begründet werden können.


Autorendiskussion