Ersetzt ma print durch printf und inkludiert stdio.h, bleibt immer noch
eine Warnung
1
‘C’ is used uninitialized in this function
Selbst wenn man C intialisiert, meckert der Compiler:
1
operation on ‘C’ may be undefined
Der Ausdruck C++ == C++ ist nämlich gar nicht erlaubt, da er
undefiniertes Verhalten bewirkt.
Diese Diskussion um undefiniertes Verhalten – insbesondere im
Zusammenhang mit den Operatoren ++ und --, hatten wir hier aber schon
oft.
PS: Der Clang-Compiler erläutert das Problem noch etwas dataillierter:
1
warning: multiple unsequenced modifications to 'C' [-Wunsequenced]
2
if ( C++ == C++ ) {
3
^ ~~
Es sind also tatsächlich die beiden ++, die sich gegenseitig beißen.
Dass der Compiler da meckert ist ja klar. Das sollte eher eine kleine
Denkaufgabe sein , die sich als gar nicht mal so einfach herausstellt.
Ich nehme mal an C wird einfach irgendwo im Speicher festgelegt und hat
halt einen bestimmten wert. Man muss sich nun ziemlich gut mit C
auskennen um genau sagen zu können wie und wann welche Befehle
ausgeführt werden.
Mein Output :
A is not A
Maximilian S. schrieb:> Man muss sich nun ziemlich gut mit C> auskennen um genau sagen zu können wie und wann welche Befehle> ausgeführt werden.
das hat aber wenig mit C zu tun. Man muss maximal seinen Compiler
kennen. Denn hier kann jeder Compiler etwas anderes liefern. (sogar jede
Version vom Compiler)
Weil man eine Variable nur einmal pro Sequencepoint zuweisen darf. Ein
Sequencepoint ist der Punkt ab dem eine Zuweisung zu einer Variablen für
den Rest des Programm sichtbar wird. Beispielsweise ist jedes ';' ein
solcher.
Peter II schrieb:> Denn hier kann jeder Compiler etwas anderes liefern. (sogar jede> Version vom Compiler)
Und sogar ein und der selbe Compiler in unterschiedlichen
Optimierungsstufen.
Es ist nicht einmal garantiert, dass das Programm überhaupt etwas
ausgibt. Es könnte (zumindest theoretisch) sogar passieren, dass bei der
Ausführung des Programms die Festplatte komplett gelöscht wird :)
Selbst in diesem Fall wäre der Compiler immer noch ISO-konform.
Frank M. schrieb:> freitagsposter schrieb:>> gcc t.c && ./a.out$ gcc t.c && ./a.out> t.c: In function 'main':> t.c:6:4: error: width of 'x' exceeds its type> t.c:15:3: warning: large integer implicitly truncated to unsigned type> [-Woverflow]> t.c:19:3: error: 'for' loop initial declarations are only allowed in C99> mode> t.c:19:3: note: use option -std=c99 or -std=gnu99 to compile your code> $ gcc -std=c99 t.c && ./a.out> t.c: In function 'main':> t.c:6:4: error: width of 'x' exceeds its type> t.c:9:18: error: expected declaration specifiers or '...' before numeric> constant> t.c:11:3: error: unknown type name 'uint33'> t.c:15:3: error: unknown type name 'uint33'> t.c:15:3: warning: overflow in implicit constant conversion [-Woverflow]
dann taugt dein compiler (einstellung) nicht für ein 33 bit integer type
Frank M. schrieb:> freitagsposter schrieb:>> dann taugt dein compiler (einstellung) nicht für ein 33 bit integer type>> Bewahrt mich aber auch vor Unsinn ;-)
nur der mut zählt ;-)
Yalu X. schrieb:> Es könnte (zumindest theoretisch) sogar passieren, dass bei der> Ausführung des Programms die Festplatte komplett gelöscht wird :)
Gibts soeinen Compiler schon? Wäre ne coole ergänzung zu Suicide Linux:
https://qntm.org/suicide, perfekt für den real programmer ;)
Daniel A. schrieb:> Yalu X. schrieb:>> Es könnte (zumindest theoretisch) sogar passieren, dass bei der>> Ausführung des Programms die Festplatte komplett gelöscht wird :)>> Gibts soeinen Compiler schon?
Zumindest hat GCC in frühen Versionen in solchen Fällen auch schon
mal "nethack" gespielt. :-)
http://feross.org/gcc-ownage/
freitagsposter schrieb:> na dann nächste runde Yalu> und auf die festplatte aufpassen!> ;-)
Ja, das ist schon interessanter :)
Dazu muss erst einmal gesagt werden, dass der typeof-Operator kein
ISO-C, sondern eine GNU-Erweiterung ist.
Auch unsigned-long-Bitfelder sind Erweiterungen, die allerdings in der
ISO-Norm explizit erlaubt werden.
Leider kann ich in der Norm auf die Schnelle keine Informationen darüber
finden, welchen Typ einzelne Bitfelder haben, wenn sie bspw. in
arithmetischen Ausdrücken verwendet werden. Bei Bitfeldern mit
erweiterten Datentypen (im vorliegenden Fall unsigned long int) scheint
der GCC mit Integer-Typen mit "krummen" Bitbreiten zu arbeiten (hier mit
"unsigned long int:33"). Im Gegensatz dazu haben beim Clang diese
Bitfelder den Typ unsigned long int, weswegen das Verhalten der beiden
Compiler an dieser Stelle unterschiedlich ist. Folgender Code
1
#include<stdio.h>
2
3
intmain(void){
4
5
struct{
6
unsignedlongx:33;
7
}s={0x1ffffffff};
8
9
printf("%9lx\n",(unsignedlong)s.x);
10
printf("%9lx\n",(unsignedlong)(s.x+1));
11
printf("%9lx\n",(unsignedlong)(2*s.x));
12
}
liefert mit dem GCC:
1
1ffffffff
2
0
3
1fffffffe
Die Ergebnisse werden also auf 33 Bit gestutzt.
Beim Clang ist das nicht der Fall, da hier für die Berechnungen
offensichtlich die volle Breite von 64 Bit genutzt wird:
1
1ffffffff
2
200000000
3
3fffffffe
Welcher der beiden Compiler nun recht hat, kann ich nicht sagen.
Möglicherweise beide, da es hier ja um eine C-Erweiterung geht, bei
denen die Norm den Compilern üblicherweise viel Freiheit lässt.
Vielleicht findet aber auch jemand die Stelle in der Norm, wo solche
Dinge genauer geregelt sind.
Was das typeof betrifft: Witzigerweise kann man – wie in deinem Beispiel
gezeigt – über dem Umweg über ein Bitfeld in einer Struktur und typeof
auch ganz gewöhnliche Variablen als Bitfeld deklarieren, so dass bei der
Arthmetik mit solchen Variablen die Ergebnisse ebenfalls auf eine krumme
Bitzahl beschnitten werden.
Das Ganze betrifft aber wohlbemerkt nur Bitfelder, die größer als int
und damit C-Erweiterungen sind. Ein int-Bitfeld wird – unabhängig von
der tatsächlichen Bitzahl – in Ausdrücken immer auf ein int erweitert.
Somit sollten sich die beiden Compiler in diesem Fall gleich verhalten.
Es ist müßig, eine "Denksportaufabe" zu undefiniertem Verhalten zu
machen, denn: Sobald das ins Spiel kommt, ist jedes beliebige Ergebnis
richtig. Es wäre sogar korrekt, wenn das Programm bei dieser if-Abfrage
beide Zweige (also sowohl if, als auch else) ausführt, oder wenn es
abstürzt oder eine unfreundliche E-Mail an deinen Chef sendet. In
comp.lang.c wurden als Beispiel immer Dämonen, die aus deiner Nase
kommen, genannt. Auch die wären nach ISO-C ein korrektes Ergebnis. ;-)
Günter X. schrieb:> Erst wird verglichen, dann wird erhöht, zweimal.
Nein. Genau das ist eben nirgends so festgelegt.
Es ist nur festgelegt, dass nach dem Vergleich C um zwei erhöht
worden sein muss (naja, mal von der fehlenden Initialisierung
abgesehen, die natürlich auch zu undefiniertem Verhalten führt –
insbesondere dann, wenn C beispielsweise vorher zufällig MAX_INT
war).
Jörg W. schrieb:> Es ist nur festgelegt, dass nach dem Vergleich C um zwei erhöht> worden sein muss
Streng genommen nicht einmal das. Undefined ist undefined, da ist
einfach jede Annahme, egal wie plausibel sie klingen mag, potentiell
falsch.
Jörg W. schrieb:> Günter X. schrieb:>> Erst wird verglichen, dann wird erhöht, zweimal.>> Nein. Genau das ist eben nirgends so festgelegt.>> Es ist nur festgelegt, dass nach dem Vergleich C um zwei erhöht> worden sein muss (naja, mal von der fehlenden Initialisierung> abgesehen, die natürlich auch zu undefiniertem Verhalten führt –> insbesondere dann, wenn C beispielsweise vorher zufällig MAX_INT> war).
Was NACH dem Vergleich passiert ist sowieso uninteressant, weil der
Zustand von C nirgendwo im Programmchen abgefragt wird. BEIM Vergleich,
VOR den beiden post increments, ist C auf jedem Fall gleich C.
Die Antwort lautet daher "A is A".
dulnik schrieb:> Die Antwort lautet daher "A is A".
Dann musst du sofort einen Bug-Report an die GCC- und Clang-Entwickler
schreiben. Mit diesen Compilern ist die Ausgabe nämlich "A is not A".
Nein, besser nicht ... war nur ein Scherz :)
Yalu X. schrieb:> PS: Der Clang-Compiler erläutert das Problem noch etwas dataillierter:> warning: multiple unsequenced modifications to 'C' [-Wunsequenced]> if ( C++ == C++ ) {> ^ ~~
Auch der gcc kann das (-Wall -Wextra)
warning: operation on 'c' may be undefined [-Wsequence-point]
if (c++ == c++) {
^
Egal was da ausgegeben wird bzw. ob überhaupt was ausgeben wird, solchen
Code schreibt man nicht (in mehrere Hinsicht) und @freitagsposter sollte
einen auf den Deckel bekommen...
steinadler schrieb:> int c = 5;> int pascal = 5;>> if (c++ == pascal)> {> print ( "C++ is Pascal\n" );> }> else> {> print ( "C++ is not Pascal\n" );> }>> Ist auch interessant ;-)
Was ist daran interessant, was will der Künstler damit zum Ausdruck
bringen?
Jörg W. schrieb:> dulnik schrieb:>> Die Antwort lautet daher "A is A".>> Noch einer, der's nicht kapieren will …
OK, ich habe zu schnell aus der Hüfte geschossen :-) Aber woran liegt
das genau? Ich dachte die post inkrement operatoren werden nach dem
Vergleich ausgeführt? Ist der Vergleich keine Expression im Sinne der
Increment Funktionalität?
dulnik schrieb:> OK, ich habe zu schnell aus der Hüfte geschossen :-) Aber woran liegt> das genau?
Daran, dass du eben innerhalb einer Anweisung nur maximal eine
Veränderung an einem Objekt durchführen darfst, egal ob nun durch
Zuweisung, pre- oder post-In- oder Dekrement-Operatoren.
Alles andere bewirkt gemäß Standard undefiniertes Verhalten.
Der Hintergrund ist einfach, dass der Standard die Compilerhersteller
nicht zu stark in ihrem Optimierungspotenzial bremsen möchte und
ihnen so genügend Freiraum gibt, die einzelnen Teiloperationen
beliebig in der Reihenfolge anzordnen.
> Ich dachte die post inkrement operatoren werden nach dem> Vergleich ausgeführt?
Genau das ist eben nicht festgelegt. Es ist nur festgelegt, dass
der Vergleich den Wert von C von vor der Inkrementierung benutzen
muss, aber das sagt nichts darüber aus, ob C zu dem Zeitpunkt, da der
Vergleich stattfindet, bereits inkrementiert worden ist oder nicht.
Also ich hab's mit gnu getestet und "A is A" (was auch zu erwarten war)
Allerdings habe ich den Fehler der fehlenden Initialisierung
kompensiert, sowas macht kein ersthafter Programmiere.
Zudem steht in allmöglichen C Büchern die Auswirkung der Schreibweise
++i oder i++
Wer das irgnoriert sollte sich nicht ärgern wenn er Stunden beim
sinnlosen debuggen verplämpert oder Stress vom Chef bekommt.
Günter X. schrieb:> Also ich hab's mit gnu getestet und "A is A" (was auch zu erwarten war)
Wieder einer, der noch nichts von diesem Thread verstanden hat. :-(
freitagsposter schrieb:> viel spass beim freitagsabtausch
Die erste Frage ist, welche Sprache das im Beispiel ist.
C oder C++ ist es wegen "void main" nicht, C# und Java haben meines
Wissens kein print, Assembler, Fortran, Pascal, Cobol und so weiter ist
es offensichtlich nicht…
Also vor der Diskussion über den Standard, erstmal die Diskussion über
die Sprache. [Hier fehlt mir jetzt doch ein passendes Smiley.]
Daniel A. schrieb:> Ja und ich mit gcc (Gentoo 4.9.3 p1.4, pie-0.6.4) 4.9.3:> [...]> A is not A
gcc 4.6.1: A is A
gcc 4.7.4: A is A
gcc 4.9.2: A is not A
Wir können ja eine Strichliste machen, wer rechter hat ;-)
Jörg W. schrieb:> Es ist nur festgelegt, dass> der Vergleich den Wert von C von vor der Inkrementierung benutzen> muss, aber das sagt nichts darüber aus, ob C zu dem Zeitpunkt, da der> Vergleich stattfindet, bereits inkrementiert worden ist oder nicht.
Der erste Teil beschreibt klar die Regel welche im zweiten Teil wieder
in Frage gestellt wird. Der Satz als solches ist nicht logisch.
Ist das eine persönliche Interpretation oder gibt es für den Satz
belastbare Beweise?
Frank M. schrieb:> gcc 4.6.1: A is A> gcc 4.7.4: A is A> gcc 4.9.2: A is not A
und jetzt bitte noch jerweils mit unterschiedlichen optimierungs
optionen.
Und selbst wenn:
Das Verhalten ist undefined! sprich, wenn du zwei mal an verschiednen
Stellen diese operation durchführst, muss nicht beide male das gleiche
Ergebnis rauskommen. Das kann völlig davon abhängen, wie der Compiler
den Code drum herum aufbaut.
Wäre es "Implementation Defined" müsste es sich immer gleich verhalten
und dokumentiert sein, aber bei "undefined" ist es "Zufall"
Günter X. schrieb:> Der Satz als solches ist nicht logisch.
Für dich vielleicht nicht, für mich schon.
Die ganze Thematik wurde doch nun ausreichend hier auf und nieder
gebetet. Wenn du's immer noch nicht glaubst, die Drafts des
ISO-C-Standards kursieren frei im Internet. Lies sie einfach.
Vlad T. schrieb:> und jetzt bitte noch jerweils mit unterschiedlichen optimierungs> optionen.
Bringt nix.
> Und selbst wenn:> Das Verhalten ist undefined! sprich, wenn du zwei mal an verschiednen> Stellen diese operation durchführst, muss nicht beide male das gleiche> Ergebnis rauskommen. Das kann völlig davon abhängen, wie der Compiler> den Code drum herum aufbaut.
Vlad, ich weiß das. Ich wollte eigentlich nur darstellen, wie unsinnig
eine Deutung des Ergebnisses ist. Deshalb schrieb ich "wer rechter hat".
Ich kann damit leben das C vor der Abfrage = 0 ist, "A is A" raus kommt
und am Ende in C 2 zu finden ist, das ergibt Sinn.
Wer meint das anders sehen zu wollen kann das gerne machen.
Das ein solcher Konstrukt in der Praxis nicht wirklich vorkommt
entschärft die Sache dann komplett :)
Günter X. schrieb:> Ist das eine persönliche Interpretation oder gibt es für den Satz> belastbare Beweise?
Da das in den Standard selbst recht verschlüsselt ausgedrückt ist, hilft
das C99 Rationale vom Standardisierungsgremium:
"The storage assignment need not take place until the next sequence
point (*). As a consequence, a straightforward syntactic test for
ambiguous expressions can be stated. Some definitions: A side effect is
a storage to any data object [...]. An ambiguous expression is one whose
value depends upon the order in which side effects are evaluated. [...].
We can then say that an unsequenced expression may be ambiguous [...] if
more than one operand contains an lvalue referencing the same object and
one or more operands specify a side-effect to that object."
Der Fall ist hier gegeben.
*: Gibts innerhalb von "C++ == C++" nicht. Appendix C in C99/C11.
PS: Es ist nicht nur offen, welcher Zweig ausgeführt wird. Es ist auch
offen, ob C hinterher um 2 oder um 1 grösser ist.
Man erhält bei "C++ == C++" beispielhaft drei Abläufe:
(1) load T1, copy, increment, store - vom linken C++
(2) load T2, copy, increment, store - vom rechten C++
(3) compare T1 and T2
(nochmal: das ist nur ein Beispiel für mögliche Abläufe).
Festgelegt ist hier nur, dass (3) nach dem Load in (1) und (2)
stattfindet. Ansonsten darf der Compiler die Teiloperationen der 3
Abläufe beliebig ineinander verzahnen.
Egal, wie viele Varianten du dir davon ausdenkst: sie erzeugen alle
undefiniertes Verhalten.
Noch was: keiner zwingt dich, beim Absenden deiner Postings eine
Mailadresse einzutragen. Wenn du dich aber schon entscheidest, eine
einzutragen, dann bitte eine, unter der dich die Forensoftware auch
erreichen kann. Durch Angabe einer nicht existierenden Adresse
outest du dich einfach nur als Troll.
Jörg W. schrieb:> Egal, wie viele Varianten du dir davon ausdenkst: sie erzeugen> alle> undefiniertes Verhalten.
dann definier sie dir doch,
es kommt das "selbe" ergebnis hervor.
>> Noch was: keiner zwingt dich, beim Absenden deiner Postings eine> Mailadresse einzutragen. Wenn du dich aber schon entscheidest, eine> einzutragen, dann bitte eine, unter der dich die Forensoftware auch> erreichen kann. Durch Angabe einer nicht existierenden Adresse> outest du dich einfach nur als Troll.
du kennst doch meine email addresse
;-)
Jörg W. schrieb:> Günter X. schrieb:>> Der Satz als solches ist nicht logisch.>> Für dich vielleicht nicht, für mich schon.
für mich auch aber ich verstehe dein "aufkochen" an dem thread nicht
jörg.
es sollte eine "denksportaufgabe" sein, nichts weiter.
dass jemand hier tatsächlich ein undefiniertes verhalten nachrechnet,
wäre mir nie in den sinn. es geht um die feststellung, dass dies nicht
im standard definiert ist.
undefiniertes "verhalten"
;-)
freitagsposter schrieb:> dass jemand hier tatsächlich ein undefiniertes verhalten nachrechnet,> wäre mir nie in den sinn. es geht um die feststellung, dass dies nicht> im standard definiert ist.
Du wolltest also nur feststellen, dass undefiniertes Verhalten nicht
definiert ist?
Andreas B. schrieb:> Sobald undefiniertes Verhalten ins Spiel kommt gibt es keine falschen> Antworten mehr. Als "Denksport" ist das nicht sehr herausfordernd.
Wie ich ja schon vor über einer Woche in
Beitrag "Re: * C++ == C++ *" geschrieben habe.
Interessant, dass hier immer noch darüber diskutiert wird. Aber
scheinbar ist es für manche doch mit einer größeren gedanklichen
Anstrengung verbunden, zu erkennen, was undefiniert bedeutet.
Rolf M. schrieb:> Aber scheinbar ist es für manche doch mit einer größeren gedanklichen> Anstrengung verbunden, zu erkennen, was undefiniert bedeutet.
Ach für mich ist "undefiniertes Verhalten" ganz einfach, ich sehe das
jeden Morgen: ab dem Zeitpunkt wenn meine Frau aufsteht bis zu ihrem
ersten Kaffee ist das alles "undefiniertes Verhalten". Da lass ich auch
lieber meine Finger davon. ;-)
Es ist doch alles ganz einfach:
Wird ein Ausdruck auf der linken Seite modifiziert, darf vor einem
sequence point nicht ein weiteres mal davon gelesen werden.
Der Compiler warnt einen dann:
1
j=i+++i;
test.c:6: warning: operation on 'i' may be undefined
Erlaubt ist z.B.: