C Gibt es eigentlich Schleifen die nicht mit break vorzeitig beendet werden können?

TomH22 schrieb:
Ein break kann in diesem Fällen die Sache einfacher machen, allerdings löst es ggf. nicht das Problem, dass man hinterher noch erfolgsabhängig irgendwelche Dinge machen muss, weswegen man die error Variable trotzdem braucht.
In eher "archaischen" Sprachen wie C sind solche Fehlerbehandlungen immer irgendein Gebastel, was schnell unübersichtlich wird. Moderne Sprachen mit Exception Handling (try/catch, etc.) und automatischen Management von Objekt Lebenszyklen erlauben da elegantere und weniger fehleranfällige Lösungen.
Dann mache ich im Fehlerfall einfach ein "throw", und kann im Exception Handler ggf. aufräumen.
Bei Systemprogrammierung stellt der Code üblicherweise transparent dar, was tatsächlich passiert. Irgendwelche vom Compiler gestellten Automatismen sind z. B. in einem Treiber wenig hilfreich, weil bestimmte Dinge in bestimmten Kontexten (z. B. in einer Interrupt-Routine) schlicht nicht (automatisch) passieren dürfen. Deshalb macht man dann die Fehlerbehandlung "zu Fuß" und nutzt dann die unmißverständlichsten Keywords der Hochsprache fürs Branching. Aussagekräftige Labels sind da übrigens wesentlich besser als ein Haufen von breaks und Abbruchbedingungen, die man erst entschlüsseln muß.

C ist allerdings auch nicht als Lehrsprache entworfen worden, sondern unter der Maxime, daß der Mensch vorm Rechner besser als die Maschine weiß, was er da tut. Das ist ja bei Anfängern meistens nie der Fall.
 
SoDaTierchen schrieb:
Hier wäre ich vorsichtig. Es ist mehr als gültig, wenn man eine Schleife zum Beispiel aus Performance-Gründen vorzeitig beendet [...]. Breaks sind etwas Gutes und nichts, was man vermeiden sollte.

Schlecht ist in der Regel nur eine Schleife, die ohne Break nie abbricht.

Schleifen kann man für vieles verwenden. [...]

Wie so oft kommt es drauf an. Eine Abbruchbedingung halte ich für notwendig, aber Breaks sind nichts schlechtes.

Wenn ich meine Schleife immer über break verlassen muss schon. Und genau das meinte ich damit eigentlich.
Ich habe so viele Schleifen gesehen, wo die Endbedingungen nur noch über die Breaks definiert wurden.

Das ist aber eben nicht der Sinn von Break.
Den hingegen beschreibst du im ersten Teil deiner Ausführung.
Es ist eine Bedingung, die die Performance erhöht oder eine zusätzliche Abbruch-Bedingung um Zeit zu sparen.
Das habe ich auch nie ausgeschlossen. Aber jede Schleife sollte auch immer ohne Break funktionieren.

Micke schrieb:
kannst du das bitte etwas ausführen ?

Einfach gesagt sollte die Grundabbruch-Bedingung der Schleife erfüllbar sein in einer endlichen Zeit.
Hier ist die Definition der Abbruchbedingung entscheidend. Ist die Falsch kann auch eine Endlosschleife entstehen oder eine Schleife sich selbst wiederholt aufrufen.

Break, Jump to etc. Also alle Bedingungen, die in die Schleife eingreifen sollten immer auch als solche gesehen und genutzt werden.
Manchmal ist es so sogar übersichtlicher und einfacher, wenn die Grundbedingung sonst sehr kompliziert wird.
Aber als primären Ausweg aus der Schleife, und ich glaube so meinte es mein Lehrer auch, sind Break und Konsorten eigentlich nicht gedacht.
 
  • Gefällt mir
Reaktionen: BeBur
Micke schrieb:
Ich fand die Erwähnung von Rekursionen recht gut, weil sie nicht ohne weiteres durch breaks verlassen werden können. Allerdings zählen diese nicht zu den Schleifen 🙂. Warum ist Stoff für einen anderen Thread.
Wie schade, dann erfahren wir doch gar nicht, ob du in theoretischer Informatik fit bist ;D.

Wechsler schrieb:
Aussagekräftige Labels sind da übrigens wesentlich besser als ein Haufen von breaks und Abbruchbedingungen, die man erst entschlüsseln muß.
Ein voreingenommener Vergleich. "Ein fauler Apfel ist besser als eine reife Orange" - ja stimmt. Gotos überführen aber trotzdem gerne mal das Programm in einen nicht vorgesehenen Zustand und werden daher zu Recht überall außerhalb von Assembler Programmierung gemieden.

Wechsler schrieb:
C ist allerdings auch nicht als Lehrsprache entworfen worden, sondern unter der Maxime, daß der Mensch vorm Rechner besser als die Maschine weiß, was er da tut.
Das würde ich eher bezweifeln, es wird wohl eher darum gehen, overhead-freie Abstraktionen zu haben, bzw. eher wenig Abstraktion und dafür näher an der Hardware dran. Vermutlich auch historisch bedingt, besser ging es vor 20-30 Jahren eben auch nicht. Das was du schreibst ist keine Maxime, sondern eine zwangsweise Konsequenz, dass man sich bei C besonders schnell in den Fuß schießen kann.
 
MichiSauer schrieb:
Einfach gesagt sollte die Grundabbruch-Bedingung der Schleife erfüllbar sein in einer endlichen Zeit.
...
Break, Jump to etc. Also alle Bedingungen, die in die Schleife eingreifen sollten immer auch als solche gesehen und genutzt werden.
Danke, dann verstehe ich langsam, warum das break so unterschiedlich wahrgenommen wird.
Sieht man Schleifen als Mittel zur Suche, ist der vorzeitige Abbruch positiv behaftet, weil dies eine erfolgreiche Suche bedeutet. Die von allein auslaufende Suche ist Ressourcenvergeudung (weil ergebnislos) .......... s. BeBur's Bsp.

Sieht man Schleifen hingegen als todo Liste von ArbeitsAnweisungen, ist es genau umgekehrt ... ein Abbruch ist eine unerwünschte Störung, weil damit Aufgaben unerledigt bleiben.

Es unterscheidet sich also nicht das Verständnis, sondern die Brille 🙂

BeBur schrieb:
Wie schade, dann erfahren wir doch gar nicht, ob du in theoretischer Informatik fit bist ;D.
nur um zum nächsten Rendevous noch etwas mitbringen zu können ;)
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: BeBur und TomH22
Wechsler schrieb:
Irgendwelche vom Compiler gestellten Automatismen sind z. B. in einem Treiber wenig hilfreich, weil bestimmte Dinge in bestimmten Kontexten (z. B. in einer Interrupt-Routine) schlicht nicht (automatisch) passieren dürfen.
Ok, bei einer Interrupt Routine oder ähnlichem Low-level Code gehe ich mit. Aber im Idealfall setzt die Interrupt Routine nur einen Semaphore oder ähnliches und alles andere wird in einem Thread gemacht. Ich habe mittlerweile einiges an Code für STM32 Microcontroller in C++ geschrieben, man kann das erstaunlich gut machen. Und wenn ich ein dünnes C++ Wrapper Objekt um z.B. einen Mutex packe, das im Destructor den Mutex freigibt, dann wird der eben sicher freigegeben wenn das Objekt out-of-scope geht.
MichiSauer schrieb:
Wenn ich meine Schleife immer über break verlassen muss schon. Und genau das meinte ich damit eigentlich.
Das ist eine sehe individuelle Sichtweise von Dir, die ich keineswegs als allgemein relevante Empfehlung betrachten würde.
Grundsätzlich geht es bei Code um Lesbarkeit und Klarheit. Und wenn ein while(1) { ….} das nur über break oder return verlassen wird, klar lesbaren und übersichtlichen Code ergibt, ist das absolut ok.
Eine wichtige Regel beim Coding ist auch, unnötigen State zu vermeiden. Wenn ich eine Hilfsvariable brauche um die Abbruchbedingung in zur Schleifenbedingung zu transportieren, dann ist das unnötiger State. Wenn man Glück hat, ist der Compiler wenigstens schlau genug, und macht aus der Konstruktion implizit einen break. Wenn nicht erhöht man die Register-Pressure (Belegung der CPU Register mit lokalen Variablen). Außerdem erhöht man das Risiko das im Schleifenrumpf noch Statements ausgeführt werden, die nicht mehr ausgeführt werden dürfen.
BeBur schrieb:
Gotos überführen aber trotzdem gerne mal das Programm in einen nicht vorgesehenen Zustand und werden daher zu Recht überall außerhalb von Assembler Programmierung gemieden.
Auch hier gilt: Wenn ein goto den Code klarer und sicherer macht, ist es akzeptabel. Natürlich sollte man immer darüber nachdenken was man tut.
Aber ich muss auch sagen, das es bei mir wirklich nur sehr selten vorkommt, dass ich goto verwende.

Die „Verteufelung“ von goto hat hauptsächlich historische Gründe, weil beim Übergang von unstrukturierten Sprachen wie Fortan und BASIC - in den damaligen Ausprägungen - Programierer das goto gewohnt waren und lernen mussten umzudenken. Heute wachsen die Anfänger mit strukturierten Sprachen auf, was das goto eigentlich kein natürlicher und naheliegender Weg ist, Code zu formulieren. Ich habe schon etliche junge Entwickler erlebt, die goto garnicht kennen…
 
TomH22 schrieb:
Ich habe schon etliche junge Entwickler erlebt, die goto garnicht kennen…
Das sind gute Nachrichten.

TomH22 schrieb:
Die „Verteufelung“ von goto hat hauptsächlich historische Gründe, weil beim Übergang von unstrukturierten Sprachen wie Fortan und BASIC - in den damaligen Ausprägungen - Programierer das goto gewohnt waren und lernen mussten umzudenken.
Die Verteufelung hatte schon etwas technischere Gründe. Dijkstra schrieb z.B. einen Artikel (dessen Überschrift er nicht wählte und mit dem erinhaltlich nicht übereinstimmt) "The goto statement considered harmful". Es gab auch ein wichtiges Paper (finde ich grad nicht) das darüber schreibt, dass die Verifikation von Programme mit Goto nicht mehr gut möglich ist. Knuth hat dann einen Artikel veröffentlicht wo das Thema sehr ausführlich beleuchtet wird "Structured Programming with go to Statements".

Jedenfalls, alle bekannten Programmier-Paradigmen wie OOP oder FP und das meiste andere Zeug dreht sich auch oder hauptsächlich darum, den Zustand "State" des Programmes zu organisieren. Genau das macht Goto aber sehr schnell sehr schwierig, weil der Sprung komplett unabhängig von Scoping, Boundaries, etc. geschehen kann.
 
@BeBur, ich stimme Dir vollständig zu. In moderneren Sprachen gibt es ja mit strukturiertem Excecption Handling Mechanismen die goto komplett unnötig machen und dabei das Paradigma der strukturierten Programmierung nicht verletzen.
In C muss man halt manchmal diese Krücke benutzen. Aber es passt halt in das Konzept von C, dass man sehr viele Dinge in die Verantwortung des Programmierers legt, die z.B. in Rust und selbst in C++ Aufgabe das Compilers und der Runtime sind. Und da gehört das goto noch zu den harmloseren Dingen.

In den 70er Jahren war goto jedenfalls noch so ein großes Problem, das eben Artikel wie der von Dijkstra enorme Beachtung erfuhren. Wenn heute jemand auf einer Konferenz einen Vortrag zum Thema goto einreichen würde, würde er vermutlich müde lächelnd abgelehnt werden.
 
  • Gefällt mir
Reaktionen: BeBur
ZuseZ3 schrieb:
Wer kann die Begründung ohne Spicken richtig vorhersagen? :D
Zumindest einen Teil (Entfernung des Loops) kenne sogar ich als jemand, der nur selten C++ Programmiert. Aber wenn wir jetzt anfangen, Kuriositäten von C/C++ zu sammeln wird der Thread hier noch auf einige hundert Seiten anwachsen ;-).
 
Die Loop wird wegoptimiert, aber dann würde man trotzdem erwarten, dass nichts ausgegeben wird.
Die Deklaration der Funktion unreachable sollte keinen Output generieren.

Allerdings kenne ich die Funktion std::unreachable um eine Warnung wegen undefiniertem Verhalten auszulösen. Ich könnte mir vorstellen, dass der Compiler die Loop als undefiniertes Verhalten erkennt und die Funktion std::unreachable aufrufen will um das deutlich zu machen. Dabei wird dann aber die im Code definierte Funktion aufgerufen.
Das ist aber nur eine Vermutung, da ich eher selten mit undefiniertem Verhalten in Code zu tun habe.
 
Aktueller clang bug, tritt mit gcc und msvc nicht auf.
 
Kein Bug, da vom C++ Standard abgedecktes verhalten.
Ich würde aber nicht widersprechen, falls jemand den C++ Standard als verbugged beschreiben würde.
 
  • Gefällt mir
Reaktionen: KitKat::new()
ZuseZ3 schrieb:
Ich würde aber nicht widersprechen, falls jemand den C++ Standard als verbugged beschreiben würde.
Die undefined behavior gibt es doch aus Performancegründen und C++ musste die daher ebenso wie C haben, da C++ so wenig wie möglich performanceeinbüßen gegenüber C einführen wollte.

Habe gerade diesen spannenden Link (wieder-)gefunden Link.

Signed integer overflow: If arithmetic on an 'int' type (for example) overflows, the result is undefined. One example is that "INT_MAX+1" is not guaranteed to be INT_MIN. This behavior enables certain classes of optimizations that are important for some code. For example, knowing that INT_MAX+1 is undefined allows optimizing "X+1 > X" to "true". Knowing the multiplication "cannot" overflow (because doing so would be undefined) allows optimizing "X*2/2" to "X". While these may seem trivial, these sorts of things are commonly exposed by inlining and macro expansion.
 
BeBur schrieb:
Die undefined behavior gibt es doch aus Performancegründen
Jaein. UB ist eine faszinierende Sache, aber im Endeffekt geht es halt darum den User auf der einen Seite einzuschränken, damit compiler auf der anderen Seite mehr optimierungsspielraum haben.
C++ macht es sich da imho zu einfach (auch wenn es damals fairerweise keinen besseren Ansatz gab), indem es einfach auflistet was user nicht dürfen. Die Liste an verboten ist aber zu lang, weshalb compilerentwickler das effektiv trotzdem nicht zu aggressiv ausnutzen dürfen um nicht andauernd C++ code der irgendwo UB hat kaputzuoptimieren.

Ein deutlich besserer Kompromiss ist imho Rust. Schau dir z.B. mal no-alias / restrict an. Gabs auch in C++ schon, user konnten es manuell hinzufügen und so dem compiler zu versprechen, dass pointer nicht aliasen.
Rust fügt das automatisch dort ein wo es korrekt ist und damit an sehr vielen Stellen. Hat sich dann rausgestellt, das LLVM (und vmtl. GCC) zahlreiche bugs in der implementierung haben, weil C++ Entwickler es zu selten genutzt haben.

https://blog.sigplan.org/2021/11/18/undefined-behavior-deserves-a-better-reputation/
ist btw. ein BlogPost von Ralf Jung.
 
  • Gefällt mir
Reaktionen: BeBur und blöderidiot
MichiSauer schrieb:
Ich erinnere mich an meinen IT-Lehrer:
Wenn ihr Break in einer Schleife braucht, habt ihr die Bedingung nicht korrekt gesetzt oder eure Annahme war falsch.
Im Nachhinein:
Das abbrechen einer Schleife ist nur sinnvoll, wenn sonst Speicher, Zeit oder anderes volläuft.

Aber im Grunde hatte meine Lehrer damals recht:
Wer eine Schleife unterbrechen muss hat schlampig programmiert.
Das stimmt nicht. Wenn ich eine Liste durchgehe und das Element welches ich suche in der Mitte der Durchgänge schon gefunden werden kann, dann bietet es sich durchaus an, ein Break zu setzen. Warum noch weiter iterieren wenn ich mein Element gefunden habe?

Und wie kann Zeit volllaufen? Versteh ich nicht. Was kann denn sonst noch volllaufen außer der Speicher?

Meines Erachtens sollten Abbruchbedingungen trotzdem sehr behutsam eingesetzt werden und am besten in nur kleinen Funktionen die schnell lesbar sind. Wenn ich 200 Zeilen Funktion habe und dort mehrere Abbruchbedingungen enthalten sind, wünschst du dem jeweiligen Entwickler dann den Tod beim Warten dieses Codeschnipsels.
 
Zurück
Oben