Schleifen - Wieso keine Sicherheit?
-
Ohne diese Vermutung belegen zu können, vermute ich einfach mal, dass die Möglichkeit eines overflows absolut erwünscht ist. Die for-Schleife in C/C++ unterscheidet sich von den Schleifen anderer Computersprachen ja auch darin, dass man den "Zähler" nicht nur stur zählen lassen, sondern so ziemlich alles damit machen kann (z.B. +1,-2,+3,-4,..., oder Fibonacci 0,1,1,2,3,5,8,...). Unter dem Gesichtspunkt gibt es keinen Grund, warum ich beschränkt werden soll, wenn ich +32766,+32767,-32768,... zählen möchte (der umgekehrte Effekt war früher bei Spielern von schlecht programmierten Spielen sehr erwünscht
).Anderer Erklärungsversuch:
Im Gegensatz zu Strings, welche "Datenspeicher" sind, handelt es sich bei der for-Schleife um eine Kontrollstruktur. Als Programmierer weißt du zur Entwurfszeit nicht, welche Daten in deinem String landen, daher muss der String gegen jeden Blödsinn abgesichert werden, die ein Benutzer damit anstellen kann.
Die for-Schleife dagegen kontrolliert DEINEN Programmfluss. Du bist der Programmier, ergo entscheidest du was und wie oft das Programm machen soll. Auch wenn die Abbruchbedingung der Schleife aus einer Benutzereingabe stammen sollte, so verfügst lediglich du über das Wissen, warum nur eine Höchstzahl an Schleifendurchläufen möglich sein soll (in deinem Bsp. ist der Zähler nur ein short, kein int. Das kann der Benutzer nicht wissen, muss also vorher abgefangen werden).Ich gebe dir allerdings recht, dass zumindest der Compiler für solche offensichtliche Fehler wie den hier
for(short i=0; i<100000; ++i){}eine Warnung ausspucken sollte (hat mein g++ 4.6.1 selbst mit -Wall -pedantic nicht gemacht
). Dass die Abbruchbedingung hier nicht erreicht werden kann ist offensichtlich - trotzdem kein Grund overflows komplett zu verbieten. Das hier funktioniert nämlich:for(short i=32000; i<-32000 || i>0; ++i){}
-
Ja, eine Warning wäre gut.
Ich wundere mich auch gerade sehr dass keine kommt, aber nachdem ich es gerade selbst probiert habe (mit MSVC) muss ich es wohl glauben
-
(gast) schrieb:
Unter dem Gesichtspunkt gibt es keinen Grund, warum ich beschränkt werden soll, wenn ich +32766,+32767,-32768,... zählen möchte
Overflow von signed Typen ist UB.
-
cvcv schrieb:
Der Standard kennt CPU-Flags nicht -> Wie sind dann Bedingungen umgesetzt?
Du darfst dir das nicht so vorstellen, dass der C++-Standard sagt, in welche Assembler-Instruktionen ein C++-Code übersetzt werden soll, sondern die Sprache läuft quasi auf einer abstrakten Maschine. Und so sagt der Standard dann auch nur, dass je nach Auswertung der Bedingung des ifs unterschiedlicher Code ausgeführt wird. Wie das zu bewerkstelligen ist, wird dabei offen gelassen.
-
Das erzeugt nicht auf allen Maschinen einen Overflow - es gibt auf der Welt nicht nur x86 und x86-64, wo sich short == 16 Bit eingebürgert hat. Unter UNICOS (das findet man auf Crays) ist short üblicherweise ganze 64 Bit breit (d.h. sizeof(short) == sizeof(int) == sizeof(long)), und da passt 32768 locker rein.
Ich wüsste auch nicht zu sagen, ob alle CPUs Overflow flaggen.
-
(gast) schrieb:
Ich gebe dir allerdings recht, dass zumindest der Compiler für solche offensichtliche Fehler wie den hier
for(short i=0; i<100000; ++i){}eine Warnung ausspucken sollte (hat mein g++ 4.6.1 selbst mit -Wall -pedantic nicht gemacht
).Solche Sachen mahnt der GCC normalerweise erst ab Wextra an (Tut er in diesem Fall auch wirklich!). Wall ist eher für Sachen, die wirkliche Laufzeitfehler ergeben. Dieses hier ist bloß eine ungewöhnliche Programmierstruktur mit genau definiertem Verhalten, welches auch keinen Fehler hervorruft.
-
SeppJ schrieb:
Dieses hier ist bloß eine ungewöhnliche Programmierstruktur mit genau definiertem Verhalten, welches auch keinen Fehler hervorruft.
Eigentlich handelt es sich hier um Undefined Behaviour. Das müsste in http://blog.regehr.org/archives/161 stehen. Schlag mich, wenns nicht so ist.
-
@SeppJ: Okay, überredet
Irgendwie muss man ja auch absichtliche "Endlos"-Schleifen programmieren können
Sachen wiewhile(1!=2){ ... if(condition){break;} }sind ja weit verbreitet.
-
(gast) schrieb:
@SeppJ: Okay, überredet
Irgendwie muss man ja auch absichtliche "Endlos"-Schleifen programmieren können
Sachen wiewhile(1!=2){ ... if(condition){break;} }sind ja weit verbreitet.
Äh,
while (true)oder einfachfor (;;)?
-
Michael E. schrieb:
SeppJ schrieb:
Dieses hier ist bloß eine ungewöhnliche Programmierstruktur mit genau definiertem Verhalten, welches auch keinen Fehler hervorruft.
Eigentlich handelt es sich hier um Undefined Behaviour. Das müsste in http://blog.regehr.org/archives/161 stehen. Schlag mich, wenns nicht so ist.
Woah! Das ist aber mal eine obskure Klausel! Ich erinnere mich zwar da dran, diese mal gelesen zu haben, aber an diese Konsequenzen hätte ich nie gedacht. Das hat ja richtig weitreichende Folgen für die Standardkonformität harmloser Programme!
Dann lass uns vielleicht lieber annehmen, dass das Programm gutes altes C89 ist mit definiertem Überlaufverhalten und ohne Klauseln über das Verhalten von Schleifen.
-
Noch ne kleine Anmerkung: In C++11 ist dieses Verhalten explizit nicht mehr auf Schleifen begrenzt, sondern gilt beispielsweise auch bei endlosen rekursiven Aufrufen. Hab die entsprechende Stelle aber gerade nicht zur Hand.
Edit: §1.10.24:
The implementation may assume that any thread will eventually do one of the following:
—terminate,
—make a call to a library I/O function,
—access or modify a volatile object, or
—perform a synchronization operation or an atomic operation.
[Note:This is intended to allow compiler transformations such as removal of empty loops, even when
termination cannot be proven. —end note]
-
@Michael E.
Ja, die ganze Geschichte mit "Programm/Funktion das nicht terminiert => UB => Compiler dürfen ganz wild Dinge wegoptimieren" hat ja durchaus für einige Diskussionen gesorgt.
Das Argument des "pro" Lagers war dabei immer dass das bestimmte Optimierungen erlaubt die sonst nicht möglich wären.Was mich aber interessieren würde: gibt es da ein gutes Beispiel für eine Optimierung, die ohne diese Regel nicht möglich wäre, und wo auch etwas sinnvolles dabei rauskommt?
Ich kann mir nämlich vorstellen dass bestimmte Optimierungen dadurch vielleicht einfacher werden, aber mir fällt nix ein wo es ohne diese Regel wirklich nicht möglich wäre.
-
Die leere Endlosschleife wird schneller ausgeführt
.Um etwas sinnvoller zu werden: Gegeben ein Programmteil
unsigned N; cin >> N; unsigned counter = 0; for (unsigned short i = 0; i < N; ++i) ++counter; // N im Folgenden nicht mehr benutzt.Könnte komplett durch
unsigned counter; cin >> counter;ersetzt werden, wenn man weiß, dass die Schleife terminiert.
Solche Fälle sollten sich leicht mehr (und sinnvoller) konstruieren lassen, wenn das Ergebnis einer Schleife für den Compiler berechenbar von den Kontrollvariablen abhängt (gar nicht so selten!), aber in einigen Ausnahmefällen die Schleife gar nicht abbrechen würde.
-
Wenn die Eingabe fehlschlägt, enthält die Variable einen undefinierten Wert.
-
314159265358979 schrieb:
Wenn die Eingabe fehlschlägt, enthält die Variable einen undefinierten Wert.
War vorher auch so.
-
314159265358979 schrieb:
Wenn die Eingabe fehlschlägt, enthält die Variable einen undefinierten Wert.
Für mich kam bisher ein Blockierskript für einzelne User nicht infrage. Wenn du jetzt allerdings jeden Beitrag von SeppJ kommentieren willst, weil du Unrecht hattest und das nicht einsehen willst, wanke ich sehr in meiner Haltung.
-
Ich hatte nicht Unrecht.
-
hustbaer schrieb:
Ich kann mir nämlich vorstellen dass bestimmte Optimierungen dadurch vielleicht einfacher werden, aber mir fällt nix ein wo es ohne diese Regel wirklich nicht möglich wäre.
Es ist einfacher, ein Programm auf Seiteneffekte zu testen, als auf Terminierung. Wenn der Compiler ersteres aber beweisen kann, darf er den entsprechenden Code eventuell trotzdem nicht wegoptimieren, wenn er Terminierung nicht zeigen kann.
-
hustbaer schrieb:
Was mich aber interessieren würde: gibt es da ein gutes Beispiel für eine Optimierung, die ohne diese Regel nicht möglich wäre, und wo auch etwas sinnvolles dabei rauskommt?
Ich hätte da eher eine andere Frage: Gibt es überhaupt sinnvolle Schleifen, welche in diese Kategorie fallen? Das Ganze scheint mir ein etwas theoretisches Konstrukt zu sein.
Ich zweifle daher an diesen "weitreichenden Folgen", wie es SeppJ gesagt hat.
Grüssli
-
(gast) schrieb:
eine Warnung ausspucken sollte (hat mein g++ 4.6.1 selbst mit -Wall -pedantic nicht gemacht
). Dass die Abbruchbedingung hier nicht erreicht werden kann ist offensichtlich - trotzdem kein Grund overflows komplett zu verbieten. Das hier funktioniert nämlich:for(short i=32000; i<-32000 || i>0; ++i){}-Wextra ist gefragt.
ethon@fleck:~$ gcc -Wall -Wextra test.cpp
test.cpp: In Funktion »int main()«:
test.cpp:10:22: Warnung: Vergleich ist durch beschränkten Wertebereich des Datentyps stets »wahr« [-Wtype-limits]