Dieser Thread ist fuer alle Fallstricke des Programmierens gedacht.
Wenn ihr etwas interessantes findet, oder selber etwas verbrochen habt, was euch kurios oder wichtig vorkommt, immer her damit.
@ Magor unter mir: Ich habe mal deinen Thread gekapert und entfremdet, da das Problem ja geloest war Ausserdem war dein Beispiel ein toller Einstieg zum Thema.
im Zuge der Arbeit am C-Projekt für die Uni (vorhin abgegeben, jetzt hoffentlich fehlerfrei und funktionstüchtig) ist mir ein Punkt an C bzw. am C-Compiler (ich nutze gcc 4.0.1) aufgefallen, der mir ein wenig seltsam vorkommt.
Es geht um eine Funktion mit definiertem Rückgabetyp, die aber nicht immer auch einen Wert zurück gibt. Bspw. folgendes:
Die Funktion foo liefert in diesem Fall gar nichts zurück und ich hatte zuerst angenommen, daß daher i seinen ursprünglichen Wert beibehält.
In meinem Fall hatte ich danach die Rückgabe daraufhin überprüft ob ein Fehler im Funktionsaufruf auftrat (dann liefert die Funktion 1 zurück), was selbst bei normaler Ausführung (also wenn kein Fehler auftrat) keinerlei Fehlermeldungen oder Programmabstürze verursacht hat.
Dennoch hat eine Analyse des Programms mit valgrind diesen Umstand moniert.
Meine Frage ist jetzt, gibt es eine Konvention oder eine sonstige Regel im C-Standard die besagt, was i hier im Beispiel nach dem Funktionsaufruf für einen Wert haben soll, oder ist das unbestimmt und letztlich dem Zufall überlassen?
Und falls es dafür keinen vorgeschriebenen Weg gibt, wieso moniert der Compiler diesen Umstand nicht und gibt zumindest eine Warnung aus?
Weiß da jemand genaueres?
Ohne das zeigt er doch nur wirkliche Fehler und kritische Warnings an.
Der obige Code ist ja valide, auch wenn du dich darauf verlaesst, dass der Rueckgabewert ggf unspezifiziert ist. (Wahrscheinlich ist das als Implementationsabhaengig markiert)
Koennte ja sein, dass der Programmierer das beruecksichtigt hat (z.B. alle moeglichen Faelle mit if abgefragt hat, bevor das Ende der Funktion erreicht ist).
C geht erstmal davon aus, dass der Programmierer WEISS, was er tut. Daher ist -Wall eine gute Idee.
Mein gcc version 4.2.3 liefert mir mit -Wall jedenfalls folgendes:
Ohne -Wall schweigt es jedoch in Ehrfurcht vor meinen Programmierskillz.
Meine Frage ist jetzt, gibt es eine Konvention oder eine sonstige Regel im C-Standard die besagt, was i hier im Beispiel nach dem Funktionsaufruf für einen Wert haben soll, oder ist das unbestimmt und letztlich dem Zufall überlassen?
Und falls es dafür keinen vorgeschriebenen Weg gibt, wieso moniert der Compiler diesen Umstand nicht und gibt zumindest eine Warnung aus?
Weiß da jemand genaueres?
...
Gleich zu Beginn: Ich bin weder der größte Experte bei noch Fan von C, daher ist meine Antwort mit Vorsicht zu genießen und besser erst aufzunehmen, wenn jemand "Stimmt so" drunter schreibt.
Aber ich denke, das kommt einfach drauf an, welche Compilerflags du verwendest. Mit -ansi -pedantic -Wall sollte er eigentlich schon meckern, bzw. es gar nicht erst kompilieren, wenn eine Funktion entgegen des Headers nichts zurückgibt, daher kann das drüberliegende Problem gar nicht auftreten.
Ohne die Flags ist GCC aber gerade mal ein etwas höherer Assembler, sprich: er setzt die Statements in Maschinencode um, ohne groß was zu prüfen. Daher wird er bei der Funktion, wenn sie nicht mit 1 aufgerufen wird, einfach zurückspringen, ohne was ins Return-Register zu schreiben. Das aufrufende Statement kann das aber natürlich nicht ahnen und weist i daher den Wert des Return-Registers zu, was auch immer der sein mag. Resultat: Chaos. *shrug*
Also, was ich denke: Falls es erfolgreich kompiliert ist das Ergebnis undefiniert, aber mit den richtigen Flags sollte sowas gar nicht erst kompilieren. Und wenn man schon in C programmiert, sollte man die Flags auch tunlichst verwenden, um zumindest einen Teil der Riesenmenge an üblichen Bugs zu vermeiden.
Edit: OK, dann höre einfach auf den, der sich auskennt. XD
--
A human is a system for converting dust billions of years ago into dust billions of years from now via a roundabout process which involves checking email a lot.
Du hast ganz recht und im Grunde haben wir ja vollkommen das selbe gesagt. Nur war ich nen Tick schneller
@ Topic
Da das Thema jetzt ja geklaehrt ist, koennte man diesen Thread ja zu einem Thread ueber Programmierfallstricke umfunktionieren. Ich bin naemlich letztens erst ueber ein Produkt eigener Dummheit gestolpert:
Jeder kennt das: Man hat ein mathematisches Konstrukt ausgearbeitet und uebertraegt das auf C(++). Alles scheint zu funktionieren, doch nach wochen oder Monaten faellt einem was auf, dass der Code nicht so tut, wie er tun sollte, und manchmal seltsamen Mist ausgibt ... Nach aufwaendigem Debuging meines mehere tausend Zeilen langen Codes ueber mehere Dateien bin ich dann auf folgendes gestossen:
Sieht soweit gar nicht verkehrt aus, und -Wall meckert auch nicht rum.
Nur leider gehen hier die Vorstellungen von Naturwissenschaftler und Compiler auseinander:
Der Naturwissenschaftler liest: Der Compiler liest: oder mit anderen Worten: oder fuer Leute ohne C-Syntax: Das das nicht gaaanz das Selbe ist, war mir dann auch klar. MMn waere das auch ein klassischer Kandidat fuer ein -Wall gewesen, denn wann will man schon mal die zweite Interpretation haben ?
Stimmt, da wäre es sinnvoll, zu meckern, genau wie bei:
(Dort meckern ja afaik auch die meisten Compiler in den meisten Sprachen.)
--
A human is a system for converting dust billions of years ago into dust billions of years from now via a roundabout process which involves checking email a lot.
Meine Frage ist jetzt, gibt es eine Konvention oder eine sonstige Regel im C-Standard die besagt, was i hier im Beispiel nach dem Funktionsaufruf für einen Wert haben soll, oder ist das unbestimmt und letztlich dem Zufall überlassen?
...
ANSI-C:
Zitat von http://flash-gordon.me.uk/ansi.c.txt
If a return statement without an expression is executed, and the
value of the function call is used by the caller, the behavior is
undefined. Reaching the } that terminates a function is equivalent to
executing a return statement without an expression.
...
Zitat von Ineluki
Das das nicht gaaanz das Selbe ist, war mir dann auch klar. MMn waere das auch ein klassischer Kandidat fuer ein -Wall gewesen, denn wann will man schon mal die zweite Interpretation haben ?
...
Na immer, wenn man sowas schreibt.
Das ist doch eine vollkommen valide Verkettung von Ausdrücken, wobei das Ergebnis des vorhergehenden Ausdrucks ein Operand des folgenden Ausdrucks ist. Vergleichsoperatoren liefern als Ergebnis immer einen Booleschen Wert, da bei dir die Variablen aber vom Typ double sind, muss der Boolesche Wert natürlich erst implizit in einen double-Wert konvertiert werden. Solange der ursprüngliche Wert nicht verändert und der Wertebereich nicht abgeschnitten wird, sind implizite Konvertierungen auch absolut in Ordnung und kein Compiler wird da eine Warnung generieren.
Meiner Meinung nach keine Warnung wert, da nur ein Verständnisproblem deinerseits.
Zitat von drunken monkey
Stimmt, da wäre es sinnvoll, zu meckern, genau wie bei:
(Dort meckern ja afaik auch die meisten Compiler in den meisten Sprachen.)
...
Auch so eine Sache. Das ist vollkommen valides C/C++ und kein Compiler wird da eine Warnung ausgeben. Sogar das strikte Java erlaubt Zuweisungen innerhalb des Bedingungsblocks, wenn die Operanden vom Typ boolean sind.
Meiner Meinung nach ein typischer Anfängerfehler, falls "==" statt "=" gemeint war, manchmal zwar auch ein Tippfehler, bei erfahrenen Programmierern sollte so ein Fehler aber sofort ins Auge stechen, also auch keine Warnung wert, vor allem da valide und oft nützlich.
Also ich finde das etwas überheblich. o_O Jedem kann mal ein Tipp- bzw. Schlampigkeitsfehler passieren, gerade bei solchen Sachen, und da man da echt praktisch nie das meint, was es wirklich bedeutet, könnte/sollte da imo schon eine Warnung erfolgen. Tut es zwar anscheinend weder bei C, noch Java oder PHP (entgegen meiner Vermutung), aber trotzdem wird vor allem a < b < c praktisch nie jemand wirklich so meinen (außer halt in Sprachen, wo's die klassische mathematische Bedeutung hat).
Dass es rein theoretisch gültig ist, und was es bedeutet, wissen wir alle.
--
A human is a system for converting dust billions of years ago into dust billions of years from now via a roundabout process which involves checking email a lot.
Also ich finde das etwas überheblich. o_O Jedem kann mal ein Tipp- bzw. Schlampigkeitsfehler passieren, gerade bei solchen Sachen, und da man da echt praktisch nie das meint, was es wirklich bedeutet, könnte/sollte da imo schon eine Warnung erfolgen. Tut es zwar anscheinend weder bei C, noch Java oder PHP (entgegen meiner Vermutung),
...
Gerade in PHP wird zumindest das:
sehr häufig verwendet. Ich benutze es eig. jeden Tag.
Das bekannteste Beispiel dürfte sein:
Btw. darf man in PHP nicht: $a < $b < $c schreiben
Geändert von Xardas der Dunkle (02.09.2009 um 21:34 Uhr)
Gerade in PHP wird zumindest das:
sehr häufig verwendet. Ich benutze es eig. jeden Tag.
...
Natürlich, da meinte ich auch mehr das andere, bzw. andere Sprachen. ^^" Aber hätte ich das jetzt alles gut ausformuliert, wäre der Post komplett unlesbar geworden. XD
Und in Java z.B. geht sowas ja nur bei booleans, da braucht man das wohl deutlich seltener. Aber klar, in PHP verwende ich's auch ständig.
Nur kann ich mir echt nicht vorstellen, dass hier jemals jemand a < b < c tatsächlich so gemeint hat. PHP verbietet das netterweise sogar. ^^ Naja, und in Java geht's ja sowieso nicht…
Dass es jemand mal unabsichtlich schreibt, kann ich mir da schon deutlich eher vorstellen, auch wenn's mir persönlich afair noch nie passiert ist.
--
A human is a system for converting dust billions of years ago into dust billions of years from now via a roundabout process which involves checking email a lot.
Gerade C legt großen Wert darauf, dem Programmierer so viel Freiraum zu lassen, wie es möglich ist. Wie Ineluki schon schrieb, ist das Motto von C: Der Programmierer weiß was er tut. Das hat Nachteile, ganz klar, so wie beispielsweise in Inelukis Fall, aber auch enorme Vorteile in der Performance und Freiheit, Punkte, die für eine Systemsprache essentiell sind. (Das heißt natürlich nicht, dass C das Non plus ultra ist und es nicht mehr besser geht, aber im Moment reden wir von C und an dieser Sprache wird sich kaum etwas so schnell ändern.)
Um zu verstehen, weshalb ich es nicht für sinnvoll halte, bei einer Verkettung wie 'a < b < c' eine Warnung zu generieren, musst du verstehen was ein Ausdruck in C ist, wie der Compiler Verkettungen auflöst, wozu implizite Konvertierung da ist, welcher Typ als Resultat einer Vergleichsoperation zurückgegeben wird, grundsätzlich alles über Typen in C, ihre Wertebereiche und wie zwischen verschiedenen Typen konvertiert wird, usw. In C gibt es keinen built-in Boolean-Typ. Bei einer Vergleichsoperation wird 0 oder 1 zurückgegeben, als int-Wert, so dass ein erneuter Vergleich mit dem zurückgegebenen Wert semantisch absolut Sinn macht, solange nicht mit einem Typ vergleichen wird, dessen Wertebereich kleiner ist, als der eines int.
Dazu kommt noch, dass man mit einer Warnung, die 'a < b < c' in Frage stellt, noch lange nicht fertig ist mit Verkettungen von Ausdrücken in C, die zwar syntaktisch und semantisch korrekt sind, aber nicht das widerspiegeln könnten, was der (unerfahrene) Programmierer gemeint haben könnte, dazu lässt C einfach zu viel Freiraum zu, und zu versuchen alles abzudecken ist schier unmöglich. Es ist Cs Charakter, wenig Regeln aufzustellen und den Programmierer machen zu lassen, wozu er Lust hat, solange es syntaktisch und semantisch valide ist und du ahnst gar nicht wie kreativ dabei manche Programmierer werden können, auch außerhalb des Obfuscated C Code Contests. Wenn man Cs Tücken ohne jahrelanger Praxis effektiv vermeiden will, sollte man C einfach nicht benutzen und zu einer Sprache wechseln, die strikter ist.
Um zu verstehen, weshalb ich es nicht für sinnvoll halte, bei einer Verkettung wie 'a < b < c' eine Warnung zu generieren, musst du verstehen…
...
Ja, danke, das verstehe ich natürlich alles. Ich kann C, und bin mir auch, wie bereits geschrieben, darüber bewusst, dass der Ausdruck theoretisch komplett korrekt ist.
Ich bin mir nur bei diesem speziellen sicher, dass der praktisch nie sinnvoll angewendet werden kann. Und da wäre, bei -Wall, mMn eine Warnung vertretbar, "Sorry, stimmt eigentlich eh, aber wolltest du das wirklich?".
Luki ist sicher kein Anfänger, und wenn selbst ihm das mal passiert ist (anscheinend vor ziemlich kurzem), kann das auch anderen guten Programmierern passieren. Und warum dann nicht davor warnen, wenn das bei anderen Sachen, die Absicht sein könnten, ohnehin schon gemacht wird?
Aber ist natürlich nur meine Meinung.
--
A human is a system for converting dust billions of years ago into dust billions of years from now via a roundabout process which involves checking email a lot.
Ich stimme Saeuferaeffchen voll zu. Ich habe schon fast ein Jahrzehnt Programmiererfahrung in C und etwa 2 Jahrzehnte Programmiererfahrung insgesammt.
Trotzdem kann einem soetwas schnell passieren, insbesondere, wenn man mathematische Formeln/Algorithmen uebertraegt. Dass C nicht erkennt, was ein bool ist, und was ein int ist, nicht zwingend so. Es ist zwar nicht als eigener Datentyp spezifiziert, aber der Compiler hat alle Informationen, das zu entscheiden, und gerade hier WEISS der compiler, sogar, dass a<b ein bool ist, und es waehre ein Leichtes fuer ihn, es von einem int zu unterscheiden. Und wann testet man ein bool schon mal auf < oder > ? Auf bools operiert man mit == true, !, || und &&, normalerweise vergleicht man sich nicht mit < oder >, denn das entspricht nicht ihrer Natur. Und das weiss auch der Compiler, weswegen er einen indirect cast von bool nach double macht, um < anwenden zu koennen.
In meinen Augen ist eine automatische Konvertierung von bool nach double zwar haeufig sinnvoll, jedoch in vielen Faellen eben nicht. Wenn ich unsigned int mit int vergleiche, warnt mich doch -Wall auch, und sei es nur, dass ich in meiner for-schleife den datentyp size_t verwende, und dann mit einem anderen berechneten Index von typ int vergleiche, auch wenn ich als Programmierer weiss, dass der Index sich im Bereich 0 bis ca. 100 bewegen wird.
-Wall gibt schon SEHR viele Warnungen aus. Da wuerde diese eine mehr wirklich nicht schaden. Ausserdem heisst eine Warnung ja nich, dass man was falsch gemacht hat, sondern dass man moeglicherweise etwas getan hat, was man so nicht wollte. Und wenn man unbedingt mit a<b<c etwas anderes meint, kann man auch explizit ein ((double)a<b)<c schreiben. Dann weiss naemlich nicht nur der compiler, was abgeht, sondern auch der Programmiere beim ueberfliegen des Quellcodes. Ein Konstrukt wie a<b<c interpretiert man beim schnellen(!) Codescreening eben NICHT als das, was es eigentlich meint.
Und mal ehrlich: Was ist Ziel des ganzen ? Eine Sprache, die jede Syntax unkommentiert uebernimmt (dann benutze einfach nicht -Wall) oder eine, die auch mal nachfragt, ob man das auch wirklich so meint. Es ist ohnehin sauberer, d.h. verstaendlicherer Code, wenn man in diesem Fall ausschreibt, dass man ein bool mit einem double vergleichen will.
Ich stimme Luki in allen Punkten zu. Allerdings ist mir tatsächlich eine sinnvolle (aber nicht wirklich schöne) Verwendung für < und > bei bools eingefallen.
ist eine kürzere Schreibweise für
Im Übrigen kennt C soweit ich weiß, sehr wohl bool, wenn auch erst seit ANSI C99.
C99 kennt _Bool, das ändert aber nichts daran, dass der Standard den Rückgabetyp der Vergleichs- und Logikoperatoren als int definiert:
Zitat von http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
Each of the operators < (less than), > (greater than), <= (less than or equal to), and >=
(greater than or equal to) shall yield 1 if the specified relation is true and 0 if it is false.92)
The result has type int.
[...]
The || operator shall yield 1 if either of its operands compare unequal to 0; otherwise, it
yields 0. The result has type int.
[...]
The && operator shall yield 1 if both of its operands compare unequal to 0; otherwise, it
yields 0. The result has type int.
...
Ich weiß jetzt nicht die genauen Implementationsdetails eines Compilers, aber in diesem Fall erfordert eine Verifikation, ob das Ergebnis des als letztes ausgewerteten Ausdrucks ein Boolescher Wert war, oder nicht, zusätzlichen Aufwand, da der Compiler mehr wissen muss, als zuvor. _Bool ist übrigens kein int.
Ansonsten:
Zitat von http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
The expression a<b<c is not interpreted as in ordinary mathematics. As the syntax indicates, it
means (a<b)<c; in other words, ‘‘if a is less than b, compare 1 to c; otherwise, compare 0 to c’’.
...
Wenn sogar die Spezifikation diesen Umstand erwähnt, werden ihn die Entwickler mit Sicherheit bedacht haben. Das Beispiel klingt nicht nur logisch, sondern ist auch definitiv nützlich. Es ist ja nicht so, als wären es Idioten und in all den Jahrzehnten von C, hätte es keine Kritik diesbezüglich gegeben.
Kyuu es geht doch gar nicht darum, dass jemand nicht verstanden hat, was a<b<c tut. Ich denke, das weiß in der Zwischenzeit jeder hier und versteht auch jeder, der sich den Code genauer anschaut.
Es geht darum, dass Warnings den Programmierer darauf hinweisen (sollten) wenn er etwas tut, was zwar syntaktisch richtig, aber in den allermeisten Fällen semantisch nicht das gewollte ist und da stimme ich zu, dass zumindest mit -Wall darauf hingewiesen werden sollte, dass a<b<c kein guter Stil da auf den ersten Blick missverständlich ist. Im Stil von a<b<c might not be what you wanted. Did you mean a<b && b<c?. Das machen manche Compiler zum Beispiel auch bei if(a = b) und da halte ich es für wesentlich weniger notwendig, weil man das tatsächlich in vielen Fällen so meint. Aber auch da wäre es unter Umständen schöner verständlicher, if((a = b) != 0) bzw. if((a = b) == TRUE) zu schreiben.
Ich weiß nicht, wie du darauf kommst, ich würde in dem Post die Funktionsweise von 'a < b < c' erklären...? Der erste Teil war zum Teil an dich gerichtet (bezüglich "Im Übrigen kennt C soweit ich weiß, sehr wohl bool, wenn auch erst seit ANSI C99") und zum Teil an Ineluki, der zuvor annahm, dass der Compiler mit booleans arbeiten würde. Tut er nicht, wie man dem Standard entnehmen kann. In C sind Rückgabetypen von Vergleichs- und Logikoperatoren keine bools und keine _Bools, sondern ints (!= bool/_Bool). Dieser Fakt wiederum erschwert die semantische Verifikation, ob einer der Operanden vom Typ bool/_Bool ist, oder nicht, und damit ist es auch nicht so trivial (entgegen Inelukis Annahme) eine Warnung oder Fehler zu generieren, wenn implizit von einem bool/_Bool konvertiert wird, da eben keine bools/_Bools in den Ausdrücken entstehen.
Der letzte Teil sollte nur noch zeigen, dass 'a < b < c' sogar im Standard erwähnt wird, da offenbar ausreichend beanstandet wurde, und dennoch gibt es bis heute keine generierte Warnung auf der Seite der Implementation, folglich als nicht kritisch genug erachtet. Und genau das ist auch meine Ansicht, siehe mein erster Kommentar diesbezüglich. Ob 'a < b < c' im mathematischen Sinn gemeint war, kann man sofort aus dem Kontext erkennen und wie gesagt, meiner Meinung nach würde so ein Fehler einem erfahrenen Programmierer nicht unterkommen. Spätestens sobald der obligatorische Test fehlschlägt, sollte man beim Überfliegen diesen Fehler entdecken.
Ich hoffe, ich habe es diesmal besser ausgedrückt. Eure Ansichten sind mir nicht egal, und ich versuche auch nicht euch meine Meinung aufzubinden (falls dies den Anschein erwecken sollte, ich bin in Diskussionen oft ehrgeizig), ich sehe es nur anders als ihr und habe versucht meinen Standpunkt wiederzugeben.
Edit: Zugegeben, in der syntaktischen Analyse könnte eine Warnung viel einfacher generiert werden, da es bei 'a < b < c' in erster Linie nicht um die Semantik geht, sondern dass diese Syntax nicht mathematisch korrekt aufgelöst wird.
Neulich in PHP passiert; Das sind immer Bugs, nach denen man lange suchen muss. Die Syntaxart ist C folgend zwar korrekt, aber verwirrend.
Die Schleife im Beispiel-Skript soll so oft durchlaufen werden, bis $allSteps = $maxSteps ist. $allSteps wird dabei in jedem Schritt um $oneStep erhöht.
Das Problem liegt hier in der vorletzten Zeile. Die Erhöhung von $allSteps wird per += durchgeführt. Wenn die zwei Zeichen umdreht und versehentlich =+ schreibt, schlägt das ganze fehl.
Für mich erschien es logisch, zuerst (=) die Zahl zu nehmen, und dann die folgende hinzuzufügen (+). Tja, pech gehabt!
--
Ich widerspreche der Nutzung oder Übermittlung meiner Daten für Werbezwecke oder für die Markt- und Meinungsforschung (§ 28 Absatz 3 und 4 Bundesdatenschutzgesetz).
Ich kann dir sogar den Grund dafür sagen. Stell dir das Ganze mal mit - statt + vor. Dann wäre für den Compiler im Fall a=-b kein Unterschied zwischen a =- b und a = -b erkennbar.
Ganz abgesehen davon wird die Schleife nicht durchlaufen, bis $allSteps == $maxSteps, sondern bis $allSteps > $maxSteps. *klugscheiß*
Aber sonst, ja, kann mir vorstellen, dass man so einen Fehler auch nicht gleich sieht, wenn man ihn mal macht. ^^"
--
A human is a system for converting dust billions of years ago into dust billions of years from now via a roundabout process which involves checking email a lot.