Wie andere gesagt haben, liegt das Problem nicht bei sich goto
selbst; Das Problem besteht darin goto
, wie die Benutzer Code verwenden und wie er das Verstehen und Verwalten von Code erschwert.
Nehmen Sie das folgende Codefragment an:
i = 4;
label: printf( "%d\n", i );
Für welchen Wert wird gedruckt i
? Wann wird es gedruckt? Bis Sie für jede Instanz goto label
in Ihrer Funktion verantwortlich sind, können Sie nicht wissen. Das einfache Vorhandensein dieses Etiketts zerstört Ihre Fähigkeit, Code durch einfache Überprüfung zu debuggen. Für kleine Funktionen mit ein oder zwei Zweigen kein großes Problem. Für nicht kleine Funktionen ...
In den frühen 90er Jahren erhielten wir einen Stapel C-Code, der eine 3D-Grafikanzeige ansteuerte und anzeigte, dass er schneller laufen soll. Es waren nur etwa 5000 Codezeilen, aber alles war drin main
, und der Autor verwendete ungefähr 15 goto
s, die in beide Richtungen verzweigten. Das war anfangs ein schlechter Code, aber das Vorhandensein dieser goto
machte es noch viel schlimmer. Mein Kollege brauchte ungefähr 2 Wochen, um den Kontrollfluss zu ergründen. Noch besser ist , diese goto
führte s im Code so dicht mit sich selbst gekoppelt , dass wir nicht konnten keine Veränderungen etwas , ohne brechen machen.
Wir haben versucht, mit der Optimierung der Stufe 1 zu kompilieren, und der Compiler hat den gesamten verfügbaren Arbeitsspeicher, dann den gesamten verfügbaren Swap-Speicher aufgebraucht und dann das System in Panik versetzt (was wahrscheinlich nichts mit den goto
s selbst zu tun hat , aber ich mag es, diese Anekdote da draußen zu werfen).
Am Ende gaben wir dem Kunden zwei Möglichkeiten - lassen Sie uns das Ganze von Grund auf neu schreiben oder schnellere Hardware kaufen.
Sie kauften schnellere Hardware.
Bodes Regeln für die Verwendung von goto
:
- Nur vorwärts verzweigen;
- Kontrollstrukturen nicht umgehen (dh nicht in den Hauptteil einer
if
oder for
oder while
Anweisung verzweigen );
- Nicht
goto
anstelle einer Kontrollstruktur verwenden
Es gibt Fälle , in denen eine goto
ist die richtige Antwort, aber sie sind selten (Ausbruch aus einer tief verschachtelten Schleife ist über den Ort nur ich es verwenden würde).
BEARBEITEN
Im Folgenden finden Sie einen der wenigen gültigen Anwendungsfälle für goto
. Angenommen, wir haben die folgende Funktion:
T ***myalloc( size_t N, size_t M, size_t P )
{
size_t i, j, k;
T ***arr = malloc( sizeof *arr * N );
for ( i = 0; i < N; i ++ )
{
arr[i] = malloc( sizeof *arr[i] * M );
for ( j = 0; j < M; j++ )
{
arr[i][j] = malloc( sizeof *arr[i][j] * P );
for ( k = 0; k < P; k++ )
arr[i][j][k] = initial_value();
}
}
return arr;
}
Jetzt haben wir ein Problem - was ist, wenn einer der malloc
Anrufe auf halbem Weg fehlschlägt? So unwahrscheinlich ein Ereignis auch sein mag, wir möchten weder ein teilweise zugewiesenes Array zurückgeben noch die Funktion mit einem Fehler beenden. Wir möchten nach uns selbst aufräumen und teilweise zugewiesenen Speicher freigeben. In einer Sprache, die bei einer fehlerhaften Zuweisung eine Ausnahme auslöst, ist dies recht einfach: Sie schreiben einfach einen Ausnahmehandler, um die bereits zugewiesenen Elemente freizugeben.
In C haben Sie keine strukturierte Ausnahmebehandlung. Sie müssen den Rückgabewert jedes malloc
Aufrufs überprüfen und die entsprechende Aktion ausführen.
T ***myalloc( size_t N, size_t M, size_t P )
{
size_t i, j, k;
T ***arr = malloc( sizeof *arr * N );
if ( arr )
{
for ( i = 0; i < N; i ++ )
{
if ( !(arr[i] = malloc( sizeof *arr[i] * M )) )
goto cleanup_1;
for ( j = 0; j < M; j++ )
{
if ( !(arr[i][j] = malloc( sizeof *arr[i][j] * P )) )
goto cleanup_2;
for ( k = 0; k < P; k++ )
arr[i][j][k] = initial_value();
}
}
}
goto done;
cleanup_2:
// We failed while allocating arr[i][j]; clean up the previously allocated arr[i][j]
while ( j-- )
free( arr[i][j] );
free( arr[i] );
// fall through
cleanup_1:
// We failed while allocating arr[i]; free up all previously allocated arr[i][j]
while ( i-- )
{
for ( j = 0; j < M; j++ )
free( arr[i][j] );
free( arr[i] );
}
free( arr );
arr = NULL;
done:
return arr;
}
Können wir das tun, ohne zu verwenden goto
? Natürlich können wir - es erfordert nur ein wenig zusätzliche Buchhaltung (und in der Praxis ist das der Weg, den ich einschlagen würde). Wenn Sie jedoch nach Orten suchen, an denen die Verwendung von a goto
nicht sofort ein Zeichen für eine schlechte Vorgehensweise oder ein schlechtes Design ist, ist dies einer der wenigen.