goto hat in einem C-Programm normalerweise nichts verloren, da es
den eigentlichen Vorteil der strukturierten Programmierung aushebelt! Es
gibt einige ganz wenige Fälle, in denen goto Sinn machen kann
(deshalb ist es Bestandteil der Sprache), und zwar z.B. zur
Fehlerbehandlung oder zum Verlassen tief verschachtelter Strukturen,
wenn es z.B. mit break nicht machbar ist. Wenn die Möglichkeit
besteht, goto zu vermeiden, dann tu es.
Die Erfinder von C schreiben selbst:
"C provides the infinitely-abusable goto statement, and labels to
branch to. Formally, the goto is never necessary , and in practice
it is almost always easy to write code without it. [...] Nevertheless,
there are a few situations where goto may find a place. The most
common is to abandon processing in some deeply nested structure, such as
breaking out of two or more loops at once. [...]"
(Kernighan & Ritchie, The C Programming Language)
"goto" wird (in C) in der Regel benutzt, wenn es entweder einen
zentralen Aussprungpunkt aus der Funktion gibt (um "finally" von C++ zu
ersetzen), oder wenn es aus einer komplexen Schleife wieder an den
Anfang gehen soll.
Vor einigen Jahr(zehnten) was es üblich, GOTO als Konstrukt vollständig
zu verbannen (die sogenannte "reine Lehre"). Wohin solche
prinzipiell-theoretischen Dogmen führen, ist ja reichlich bekannt.
Du solltest Dich bei der Architektur Deines Programms davon leiten
lassen, dass die einzelnen Programmteile übersichtlich und verständlich
sind, und das die einzelnen Themenbereiche sinnvoll voneinander getrennt
sind.
Ein Programm ist perfekt, wenn man nichts mehr weglassen kann.
Ein Programm ist dann perfekt, wenn es tut was es soll und der
Programmcode übersichtlich und nachvollziehbar ist.
Dazu gehört auch eine gewisse Anzahl an Kommentaren, wo man seine
Überlegungen niederlegt, um das ganze auch dann noch zu verstehen, wenn
man längst auf einer anderen Baustelle denkt.
Dazu gehört eben auch, dass man nicht wild im Code hin und her springt.
Eben versucht etwa Goto mit Bedacht einzusetzen.
Literatur zum Thema: Donald E. Knuth, "Literate Programming".
Kapitel 2 dort heißt "Structured Programming with go to statements".
Darin setzt er sich mit der "reinen Lehre" und allem für und wider
zu go to statements auseinander.
Hier gibt es meiner Meinung nach zwei Kriterien:
1. Was ist für einen menschlichen Leser verständlicher?
2. Was passiert im Falle einer Programm-Änderung?
zu 1:
Saubere Strukturierung per Code-Einrückung und Klammerung (mit
geschweiften Klammern) zeigt dem Leser sofort und machvollziehbar, wo es
lang geht. Das ist mit Sprunglabeln nicht unbedingt erreichbar.
Das GOTO-Konstrukt fällt aus dieser Struktur heraus - es zerstört sie.
zu 2:
Wenn die Struktur des Programms sich ändert, dann vergißt man gerne die
GOTO's und "nagelt sich so ganz gewaltige Eier auf die Schiene"! Ich
spreche hier aus eigener harter Erfahrung.
Noch eine persönliche Anmerkung:
Wenn ich in den letzten 20 Jahren in meiner beruflichen Umgebung
(professionelle Software-Entwicklung) GOTO-behafteten Code gesehen habe,
dann war diesem Code genau anzusehen, daß der Autor seine krausen
Gehirnwindungen vor Betätigen der Tastatur nicht geordnet hatte.
Dementsprechend hoch war dann auch der jeweilige Aufwand für Debugging
und Wartung.
Bernhard
Bernhard R. wrote:
> Saubere Strukturierung per Code-Einrückung und Klammerung (mit> geschweiften Klammern) zeigt dem Leser sofort und machvollziehbar, wo es> lang geht.
Du hast noch keinen Code mit 20 Einrückungsebenen gesehen. Auf sowas
kommt man nämlich zuweilen, wenn goto strikt verboten ist, und man
daher immer mehr if-Anweisungen schachteln muss, um den Rücksprung
im Falle eines früh erkannten Fehlers zu umgehen.
Ansonsten kann ich dir die Lektüre von Knuth empfehlen.
Jörg Wunsch wrote:
> Bernhard R. wrote:>>> Saubere Strukturierung per Code-Einrückung und Klammerung (mit>> geschweiften Klammern) zeigt dem Leser sofort und machvollziehbar, wo es>> lang geht.>> Du hast noch keinen Code mit 20 Einrückungsebenen gesehen. Auf sowas> kommt man nämlich zuweilen, wenn goto strikt verboten ist, und man> daher immer mehr if-Anweisungen schachteln muss, um den Rücksprung> im Falle eines früh erkannten Fehlers zu umgehen.>> Ansonsten kann ich dir die Lektüre von Knuth empfehlen.
Dem kann ich mich nur anschließen. In PHP und JavaScript vermisse ich
die Goto-Anweisung deshalb desöfteren weil man sie bei der
Fehlerbehandlung wirklich brauchen kann. Mit If usw. kann man hier
wirklich nur umwege gehen. Und die Verschachtelungen machen das ganze
nicht übersichtlicher.
lg PoWl
> In PHP und JavaScript vermisse ich die Goto-Anweisung deshalb> desöfteren weil man sie bei der Fehlerbehandlung wirklich brauchen> kann.
Dafür gibt es Excpetions (try-catch). Die ersetzen insbesondere auch
den longjmp von C. Für Sprünge innerhalb einer Funktion sind sie
vielleicht etwas überdimensioniert, was aber bei Skriptsprachen keine
so große Rolle spielt.
stimmt.. aber entsprechen exceptions nicht eigentlich auch einer
if-struktur + fehler-variable?
wenn nicht, muss ich mich damit nochmal beschäftigen
lg PoWl
Nee, exceptions sind eher ein OO-Konzept, daher auch entsprechend
schwergewichtig (kosten also einiges an Codegröße). Die Auflösung,
wohin die Exception tatsächlich geworfen wird, erfolgt gewissermaßen
als "late binding" erst zur Laufzeit.
Weiß nicht, inwiefern der Compiler, wenn die Auflösung zur Compilezeit
schon feststeht, dass dann ggf. optimiert.
Disclaimer: das ist das, wie ich Exceptions verstanden habe. Ich habe
sie noch nicht viel benutzt, insbesondere nicht in Sprachen wie C++
(in Python schon eher mal, aber da ist ja eh' alles late binding).
Paul Hamacher wrote:
> stimmt.. aber entsprechen exceptions nicht eigentlich auch einer> if-struktur + fehler-variable?
Wie ein Compiler das realisiert bleibt ihm überlassen. Da exceptions
aber oft funktionsübergreifend arbeiten können, sind Fehlervariablen
dabei eher unwahrscheinlich. Das erinnert, vereinfacht ausgedrückt, eher
an setjmp/longjmp, plus automatischer Freigabe diverser dabei über Bord
gehender Variablen abgebrochener Funktionen über Analyse des
Stack-Inhalts. Ist aber ein durchaus komplexes Thema.
Also ich benutze "gotos" alle Nase lang. Wenn man es sich mal genauer
anguckt, ist nämlich jedes switch/case eine astreine Goto-Anweisung.
Durch den ":" ist das auch deutlich ersichtlich und man kann ja auch
beliebig in anderen Code hineinspringen.
Sehr störend ist allerdings das "break" als Beenden eine Case-Anweisung
und zum Verlassen einer Schleife. Da muß man oft viele if-Anweisungen
schachteln, statt einer switch-Anweisung.
Wenn ich von den vielen geschachtelten "ifs" die Nase voll habe, nehme
ich auch mal ne Unterfunktion und kann dann mit "return" in nem "case"
die Schleife verlassen.
Peter
@Peter Dannegger
> Wenn ich von den vielen geschachtelten "ifs" die Nase voll habe, nehme> ich auch mal ne Unterfunktion und kann dann mit "return" in nem "case"> die Schleife verlassen.
Du sprichst mir aus der Seele...
>> In PHP und JavaScript vermisse ich die Goto-Anweisung deshalb>> desöfteren weil man sie bei der Fehlerbehandlung wirklich brauchen>> kann.>> Dafür gibt es Excpetions (try-catch).
Die sind, was die Codestrukturierung angeht, ähnlich wie goto, nur
schlimmer, da sie nicht nur innerhalb einer Funktion, sondern eben auch
noch über Aufrufgrenzen hinweg springen.
> Das erinnert, vereinfacht ausgedrückt, eher an setjmp/longjmp,
GCC kann optional so gebaut werden, daß er setjmp/longjmp verwendet, um
Exceptions zu implementieren.
> plus automatischer Freigabe diverser dabei über Bord> gehender Variablen abgebrochener Funktionen über Analyse des> Stack-Inhalts.
Das ist ein großer Vorteil von C++ gegenüber vielen anderen
objektorientierten Sprache, die die Ressourcen nicht automatisch
freigeben. Da muß man das immer explizit von Hand tun.
Zitat aus dem Wikiartikel oben:
"In der Praxis hat sich jedoch gezeigt, dass der Verzicht auf Goto zwar
möglich ist, jedoch in einigen Fällen zu sehr aufwändigen Konstrukten
führt. Von einigen Entwicklern wurde auf der Linux Kernel Mailing List
die häufige Verwendung von Goto im Quellcode von Linux diskutiert. Linus
Torvalds sagte dabei, dass die Verwendung von Goto die Lesbarkeit des
Quellcodes in vielen Fällen sogar deutlich erhöhen könne."
Am Linux Kernel schreiben genug Leute mit, als dass die Meinung als
fundiert gelten kann.
Ist doch mit allen Sachen so, man kann auch die Operatoren in C/C++ so
einsetzen, dass man genau garnix mehr versteht. Leute die die Disziplin
haben um damit umgehen zu können benutzen es da wo's Sinn macht... alle
anderen bleiben bei Java ;-)
Hallo, Leute.
Ich sehe, die Meinungen gehen weit auseinander.
Nun, ich vertrete die Meinung, daß goto ruhig verwendet werden kann, da
es, insbesondere für Anfänger, einfacher ist Codes zu erstellen.
Die Verwendung von while-Schleifen, wird sicherlich in
Interrupt-Routinen, wo Anfänger nicht rumrühren, seine Vorteile haben.
Grüße, Axel.
Axel Lemke wrote:
> Hallo, Leute.>> Ich sehe, die Meinungen gehen weit auseinander.>> Nun, ich vertrete die Meinung, daß goto ruhig verwendet werden kann, da> es, insbesondere für Anfänger, einfacher ist Codes zu erstellen.
Seh ich gerade umgekehrt.
Gerade Anfänger sollten die Finger von goto lassen.
goto hat durchaus seine Berechtigung, will aber weise und mit
Umsicht eingesetzt werden. Anfängern fehlt hier ganz einfach
die Erfahrung dazu. Dementsprechend sehen dann auch die Programme
aus und sind auch entsprechend fehleranfällig.
> Nun, ich vertrete die Meinung, daß goto ruhig verwendet werden kann, da> es, insbesondere für Anfänger, einfacher ist Codes zu erstellen.
Naja, insbesondere Anfänger wissen aber noch nicht, wo sie es einsetzen
sollten und wo nicht.
> Die Verwendung von while-Schleifen, wird sicherlich in>Interrupt-Routinen, wo Anfänger nicht rumrühren, seine Vorteile haben.
Hm?
Seh ich auch so, das schwierigste wenn man mit Programmieren anfängt
ist, ein sauberes Programm zu schreiben. Irgendwie zusammenfrickeln kann
jeder, aber wehe dann funktioniert etwas nicht, oder der Code soll um
irgendwas erweitert werden... oder jemand anders soll sich 3 Jahre
später in den Code reinarbeiten.
GOTO erfordert sehr viel Disziplin, C und C++ allgemein erfordern viel
Disziplin.
Ich schätze mal, ich verwende goto in etwa <1% meiner Programme, ganz
einfach deshalb weil es einfach nahezu fast immer unnötig ist. Das
einzige Programm das mir jetzt spontan einfällt ist eines mit mehreren
verschachtelten Schleifen, bei denen manchmal zu einem bestimmten Punkt
weiter vorne gesprungen wird, um ein Display wieder komplett zu
refreshen. Vermutlich wäre es auch ohne goto gegangen, aber dann hätte
ich mehrere Schleifen abbrechen müssen usw, also auch ziemlich
komplizierte Sachen machen müssen.
Für Anfänger gilt eindeutig: Finger weg von goto !
Axel Lemke wrote:
> GOTO oder While-Schleife?
Boah, es wagt jemand und öffnet die Büchse der Pandora!
Dabei kennt gelbst Java das Schlüsselwort "goto" -- allerdings nur, um
mit einer Fehlermeldung à la "javac: goto ist böööse" das Weiterarbeiten
zu verweigern...
Die goto-Welt spaltet sich also in Dogmatiker und Nostalgiker, deren
BASIC-Sprachfehler bis dato noch kein Logopäde zu heilen in der Lage
war.
Wie dem auch sei, die wahre Schönheit von goto offenbart erst GNU-C,
welches ihm durch indirekte Sprünge vollkommene Freiheit schenkt. Zu
bestaunen in folgendem Beispiel-Leckerli.
1
voidfoo(void**label)
2
{
3
goto*label;
4
}
avr-gcc bastelt daraus folgerichtig:
1
foo:
2
push r24 ; 11 indirect_jump/2 [length = 3]
3
push r25
4
ret
In einem einzigen Projekt realisiere ich so eine Menue-Struktur, für die
sich switch/case (wegen beschränkter Resourcen und unweigerlicher
Expandierung des Arguments) als zu breit erwies.
bon appétit
Hmm. Das ließe sich mit einem Funktionspointer sicherlich kaum weniger
"elegant" abbilden:
1
voidfoo(void(label)(void))
2
{
3
label();
4
}
Gut, der Compiler wird hier noch den Stack für den Funktionsaufruf
vorbereiten, die verwendeten Funktionspointer müssen anders aussehen als
die (auf welche Weise eigentlich gewonnenen?) Argumente der ersten
Variante,
aber im Endeffekt sehe ich keinen sehr großen Unterschied.
O.k. diese Funktion wird compiliert, aber wie verwendet man sie?
Es ist mir nicht gelungen ihr Labels zu übergeben.
Also eine völlig nutzlose Funktion.
Peter
>> [...] Funktionspointer müssen anders aussehen als> die (auf welche Weise eigentlich gewonnenen?) Argumente der ersten> Variante,> aber im Endeffekt sehe ich keinen sehr großen Unterschied.
Function calls haben wie gesagt Nebeneffekte und sind nicht vergleichbar
mit goto. Auch sind diese Sprünge nicht zu anderen Funktionen hin
sinnvoll, da man dazu einen Kontext herstellen/restaurieren müsste
(longjump).
Hier handelt es sich weniger um C-Code als um mehr oder weniger
portierbaren Assembler, finde ich.
Georg-johann L. wrote:
Hmm. Und warum ist das jetzt besser als
1
voidfoo(void)
2
{
3
while(1)
4
{
5
switch(getI())
6
{
7
case0:
8
...
9
break;
10
11
case1:
12
...
13
break;
14
15
...
16
17
default:
18
}
19
}
20
}
Mal abgesehen davon, dass mich das default davor schützen
kann, dass getI() etwas ausserhalb des Bereichs zurückliefern
kann und der goto bei dir ins Nirvana geht.
Dir ist hoffentlich schon klar, dass jeder halbwegs ernst zu
nehmende Compiler, so einen switch-case unter der Voraussetzung,
dass die case Label halbwegs 'schöne' Werte haben, in genau so eine
Sprungleiste umwandelt.
Fazit: Viel Lärm um nichts.
Karl heinz Buchegger wrote:
> Hmm. Und warum ist das jetzt besser als>
1
>switch(getI())
2
>
>> Dir ist hoffentlich schon klar, dass jeder halbwegs ernst zu> nehmende Compiler, so einen switch-case unter der Voraussetzung,> dass die case Label halbwegs 'schöne' Werte haben, in genau so eine> Sprungleiste umwandelt.
Wer behauptet, das sei besser...?
Es ergab sich in einer Situation mit vorgegebener Hardware und
vorgegebener Funktionalität. Da die Hersteller jedes Cent sparen, war
der Flash entsprechend knapp und mit switch eben nicht ausreichend --
andere Stellen waren schon überarbeitet.
Wenn man ans Resource-Ende kommt, wird die Sache eben zusehends
aufwändig was Entwicklungszeit, Portierbarkeit, Unterstützung und Nerven
angeht.
Ich behaupte jetzt mal, GCC ist ein halbwegs ernst zu nehmender
Compiler. GCC hat mehrer Alternativen zur Umsetzung eines switch wie
Sprungtabellen, Vergleiche, explizierte binäre Suche.
Man ist also gezwungen, zu schauen was der Compiler so treibt und die
Quelle des Projekts und/oder des Compilers (falls er quelloffen ist)
dahingehend anzupassen, dass die verfluchte Software reinpasst. In
besagtem Falle "und".
Mir würds ja schon reichen, wenn der avr-gcc bei "ISR(...) { PORTA = 0;
}" nicht schon den halben Registersatz wegpusht... aber das is nen
andres Thema.
Ansonsten geht auch noch schön:
ist besser.
Grund: "return" ist keine Funktion, sondern ein unärer Operator, wie
z.B. auch "~".
Bei return(1) kommt zwar auch das richtige raus, aber wenn man sich
überlegt was
1
return(1)+2;
macht, wird einem auffallen das die Klammern eher kontraproduktiv sind.