peter müller schrieb:> ich verstehe nicht, was in der unteren Anweisung (work. ....) passiert,
Das musst du nicht verstehen, weil es Murks bzw. (auf neudeutsch)
"undefined behavior" ist.
Der Programmierer wollte wohl schreiben
1
work.Output_Pulse|=_BV(uc_bit);
oder
1
work.Output_Pulse=work.Output_Pulse|_BV(uc_bit);
Diese beiden Varianten wirst du sicher verstehen, weil sie korrektes C
sind.
Spendiere dem Code doch mal die erforderliche Anzahl an Semikola.
Ich glaube nicht, dass es umwerfend Toll ist, mit Variablen (uc_bit),
die nicht initialisiert wurden, zu arbeiten.
Was _BV() macht, geht wahrscheinlich niemanden etwas an...
Konstrukte wie:
Var_A = Var_B = Zahl; // sind Hässlich, aber in C erlaubt.
Wahrscheinlich auch:
Var_A = Var_B |= Zahl;
Habe es aber noch nie genutzt.
work.Output_Pulse = work.Output_Pulse |= _BV(uc_bit);
Ist doch auch korrektes C. Jedenfalls so wie ich C verstanden habe.
Zuerst wird die Zuweisung
work.Output_Pulse |= _BV(uc_bit);
ausgeführt.
Danach noch die Zuweisung
work.Output_Pulse = (Ergebnis der ersten zuweisung);
Die zweiter Zuweisung ändert nichts, weil es sowas wie
i = i ;
ist. Aber syntaktisch korrekt ist das schon.
Yalu X. schrieb:> Das musst du nicht verstehen, weil es Murks bzw. (auf neudeutsch)> "undefined behavior" ist.
Kannst Du das noch etwas näher erläutern? Konstrukte wie
1
constuint8_tb=0x01;
2
uint8_ta=0;
3
a=a|=b;
sind doch erlaubt, wenn auch gewöhnungsbedürftig. Gibt es da eine andere
Regel für structs?
Viele Grüße
W.T.
PittyJ schrieb:> work.Output_Pulse = work.Output_Pulse |= _BV(uc_bit);>> Ist doch auch korrektes C. Jedenfalls so wie ich C verstanden habe.
Syntaktisch ja, aber das Verhalten ist undefiniert, da work.Output_Pulse
mehr als einmal geschrieben wird, ohne daß ein Sequenzpunkt dazwischen
liegt. Das ist wie bei
Noch kurz etwas zur Erläuterung:
PittyJ schrieb:> Zuerst wird die Zuweisung> work.Output_Pulse |= _BV(uc_bit);> ausgeführt.> Danach noch die Zuweisung> work.Output_Pulse = (Ergebnis der ersten zuweisung);
Diese Reihenfolge ist so nicht vorgeschrieben.
Es wird im Prinip zuerst ausgeführt:
1
tmp=work.Output_Pulse|_BV(uc_bit);
(Wobei tmp das Register ist, in dem das Ergebnis gespeichert wird)
Danach hat der Compiler dann die Wahl, ob er
1
work.Output_Pulse=tmp;
2
work.Output_Pulse=work.Output_Pulse;
oder
1
work.Output_Pulse=work.Output_Pulse;
2
work.Output_Pulse=tmp;
macht. Die beiden Schreibzugriffe können beliebig geordnet sein. Erst
mit dem nächsten Sequenzpunkt, der nach dem Gesamtausdruck ist, müssen
sie alle abgeschlossen sein.
Hallo,
danke für die Antworten, jetz ist mir das klar, der Term ist identisch
mit
work.Output_Pulse |= _BV(uc_bit);
-oder-
work.Output_Pulse = work.Output_Pulse | _BV(uc_bit);
Zu _BV habe ich - leider jetzt erst - hier was gefunden:
Beitrag "was beudetet _BV ?"
Offen bleibt für mich dann nur noch die Frage, WARUM jemand solchen Code
aussondert.
VG
peter
Ich habe gefunden
http://en.wikibooks.org/wiki/C_Programming/Variables
x = y = z is really shorthand for x = (y = z)
Danach wäre die Reihenfolge vorgeschrieben, und zwar rückwärts.
Also ist das auch eindeutig und nicht undefiniert.
PittyJ schrieb:> Ich habe gefunden>> http://en.wikibooks.org/wiki/C_Programming/Variables>> x = y = z is really shorthand for x = (y = z)
Im vorliegenden Beispiel haben wir aber x = x = y gehabt!
> Danach wäre die Reihenfolge vorgeschrieben, und zwar rückwärts.> Also ist das auch eindeutig und nicht undefiniert.
Es ist nicht undefined behavior, weil eine Auswertungsreihenfolge unklar
wäre, sondern weil x zweimal beschrieben wird, ohne daß ein Sequenzpunkt
dazwischenliegt.
Ganz andere Baustelle.
PittyJ schrieb:> Danach wäre die Reihenfolge vorgeschrieben, und zwar rückwärts.> Also ist das auch eindeutig und nicht undefiniert.
Du verwechselst 'operator precedence' mit 'order of evaluation'. Das
sind 2 paar Schuhe.
Das Problem mit dem fehlenden Sequenzpunkt und der Garantie des
Abschlusses von Nebeneffekten (ja, die eigentliche Zuweisung ist ein
Nebeneffekt des Assignments) bleibt weiterhin bestehen. Sobald du
zwischen 2 Sequenzpunkten (in erster Näherung kann man sagen: von einem
; zum nächsten ;) 2 schreibende Zugriffe auf ein und dieselbe Variable
hast, hast du undefiniertes Verhalten (undefined behavior). Wir denken
uns das ja nicht aus, sondern zitieren das aus der Sprachdefinition. Und
das blöde daran ist: die Compilerbauern wissen über solche Dinge
Bescheid und schmettern deinen Bug-Report knallhart mit einem Verweis
auf den C-Standard ab.
Fred schrieb:> Es ist nicht undefined behavior, weil eine Auswertungsreihenfolge unklar> wäre, sondern weil x zweimal beschrieben wird, ohne daß ein Sequenzpunkt> dazwischenliegt.
Das ist zwar alles schön und gut. Aber solange die Zuweisung selber
keine Seiteneffekte hat - was beim gezeigten Code angenommen werden kann
- ist der Effekt des Codes der gleiche. Einmal wird die Variable erst
mit dem alten und dann mit dem neuen Wert geschrieben. Und das andere
Mal zweimal mit dem neuen Wert. In jedem Fall steht hinterher der neue
Wert drin.
Ein Seiteneffekt könnte z.B. darin bestehen, daß die struct auf ein I/O
Register gebunden ist und der Schreibzugriff eine Aktion der Hardware
auslöst. Oder wenn es in Wirklichkeit C++ ist und operator = beliebigen
Anwendercode enthalten kann.
Zusammenfassend: ja, der Code ist dämlich. Aber zumindest im Kontext
wohl harmlos.
Wobei sich mir gerade eine Frage stellt. Wenn die Variable x nicht
volatile ist, darf der Compiler dann eine Zuweisung "x=x" wegoptimieren
oder nicht?
XL
Axel Schwenke schrieb:> Das ist zwar alles schön und gut. Aber solange die Zuweisung selber> keine Seiteneffekte hat
Das schreiben des Wertes in die Variable IST der Seiteneffekt!
Ein Assignment ist in erster linie ein arithmetischer Ausdruck, der
einen Wert liefert. So wie jeder andere auch. Und als Seiteneffekt wird
irgendwann vor dem nächsten sequence point dann auch tatsächlich der
Wert in den lvalue geschrieben.
das unterscheidet sich in nichts von
1
j=++i;
i++ ist ein arithmetischer Ausdruck, der den um 1 erhöhten Wert liefert.
Und als Nebeneffekt wird irgendwann i neu beschrieben.
Lass von der Vorstellung ab, das eine Zuweisung in C irgendwas
besonderes wäre. Das ist sie nicht! Eine Zuweisung ist ein Operator, so
wie es +, -, *, / %, .... auch sind. Das ist eine ganz normale Operation
die einen Wert liefert und einen Seiteneffekt hat.
Axel Schwenke schrieb:> - ist der Effekt des Codes der gleiche. Einmal wird die Variable erst> mit dem alten und dann mit dem neuen Wert geschrieben. Und das andere> Mal zweimal mit dem neuen Wert. In jedem Fall steht hinterher der neue> Wert drin.
Ich überleg gerade, ob das auch dann noch gilt, wenn während der
Zuweisung ein impliziter Cast vorgenommen werden muss.
Denn im allgemeinen Fall ist es nicht egal, ob bei
1
a=b=c;
a den Wert von b oder von c kriegt.
Ja nach Datentypen können die unterschiedlich sein.
Definiert ist es so, dass der Wert von b genommen wird. Und zwar der
Wert den b tatsächlich bekommt (oder bekommen hat)
Axel Schwenke schrieb:> Fred schrieb:>> Es ist nicht undefined behavior, weil eine Auswertungsreihenfolge unklar>> wäre, sondern weil x zweimal beschrieben wird, ohne daß ein Sequenzpunkt>> dazwischenliegt.>> Das ist zwar alles schön und gut. Aber solange die Zuweisung selber> keine Seiteneffekte hat - was beim gezeigten Code angenommen werden kann> - ist der Effekt des Codes der gleiche.
Der Seiteneffekt der Zuweisung ist, daß die Variable nachher einen neuen
Wert hat. Das klingt in dem Fall etwas merkwürdig, weil das der
Hauptzweck einer Zuweisung ist, aber so ist der Begriff "side effekt"
eben definiert.
Oder um mal aus der ISO-Norm (C99, neuer hab ich nicht) zu zitieren:
"Accessing a volatile object, modifying an object, modifying a file, or
^^^^^^^^^^^^^^^^^^^
calling a function that does any of those operations are all side
effects, which are changes in the state of the execution environment.
Evaluation of
an expression may produce side effects. At certain specified points in
the execution sequence called sequence points, all side effects of
previous evaluations shall be complete and no side effects of subsequent
evaluations shall have taken place."
Und da liegt das Problem. Es wird ein Objekt zweimal modifiziert, ohne
daß ein Sequenzpunkt dazwischen liegt. Daß einer der beiden
Seiteneffekte vollständig abgeschlossen ist, ist aber erst nach dem
nächsten Sequenzpunkt garantiert, also darf auch dann erst ein weiterer
Seiteneffekt das selbe Objekt modifizieren. Das steht auch nochmal ganz
explizit in der ISO-Norm:
"Between the previous and next sequence point an object shall have its
stored value modified at most once by the evaluation of an expression.
Furthermore, the prior value shall be read only to determine the value
to be stored."
> Einmal wird die Variable erst mit dem alten und dann mit dem neuen Wert> geschrieben. Und das andere Mal zweimal mit dem neuen Wert. In jedem Fall> steht hinterher der neue Wert drin.
Nicht unbedingt. Meine Ausführung oben war noch etwas zu kurz, deshalb
sieht es dort erstmal so aus, als wäre das Ergebnis trotzdem das
gleiche. Aber auch eine simple Zuweisung wird intern als zwei Aktionen
ausgeführt: Wert lesen und Wert schreiben. Das hat zur Folge, daß das
da:
Rolf Magnus schrieb:> work.Output_Pulse = work.Output_Pulse;> work.Output_Pulse = tmp;
auch so ausgeüfhrt werden kann:
1 work.Output_Pulse lesen
2 tmp nach work.Output_Pulse schreiben
3 den in Aktion 1 gelesenen Wert nach work.Output_Pulse schreiben
Das wäre nach ISO C genauso erlaubt.
> Wobei sich mir gerade eine Frage stellt. Wenn die Variable x nicht> volatile ist, darf der Compiler dann eine Zuweisung "x=x" wegoptimieren> oder nicht?
Ja. x=x ist zwar nicht seiteneffektfrei, auch wenn der geschriebene Wert
gleich dem gelesenen ist, aber es gibt auch noch die sogenannte
as-if-Regel, die besagt, daß der Compiler den Code beliebig ändern darf,
solange das "observable behavior" sich nicht ändert, und das tut es bei
dieser Zuweisung nicht.
Karl Heinz schrieb:> Definiert ist es so, dass der Wert von b genommen wird. Und zwar der> Wert den b tatsächlich bekommt (oder bekommen hat)
Eine besonders böse Variante davon habe ich mal selbst erlebt und
deshalb auch in die avr-libc-FAQ aufgenommen. Man nehme einen
ATmega1281. Alle IO-Ports sollen Ausgang werden. Weil man faul ist,
schreibt man das so:
1
DDRA=DDRB=DDRC=DDRD=DDRE=DDRF=DDRG=0xff;
Der Effekt: in allen DDRx steht danach 0x3f drin. Warum? Weil DDRG
nur mit 6 Bits implementiert ist und die oberen beiden Bits beim
Rücklesen 0 ergeben … Davon abgesehen ist noch die Performanceinbuße,
dass ja jedesmal der Wert vom rechts danebenstehenden IO-Register
rückgelesen werden muss, statt aus einem CPU-Register genommen zu
werden.
Axel Schwenke schrieb:> Aber solange die Zuweisung selber keine Seiteneffekte hat - was beim> gezeigten Code angenommen werden kann - ist der Effekt des Codes der> gleiche.
Undefined behaviour ist undefined behaviour. Der Compiler kann dann
auch gleich 42 in das Register schreiben, und hätte seine Pflicht
erfüllt.
Du darfst an etwas, was im Standard als “undefined” bezeichnet ist,
keinerlei Logik anwenden wie „bleibt ja trotzdem dasselbe“.
Jörg Wunsch schrieb:> Du darfst an etwas, was im Standard als “undefined” bezeichnet ist,> keinerlei Logik anwenden wie „bleibt ja trotzdem dasselbe“.
Vor allen Dingen nicht einzelne Sonderfälle herauspicken, bei denen
trotz undefined behaviour das richtige rauskommt. Denn der Standard ist
immer versucht, einen Problemkreis in voller Allgemeinheit abzuhandeln.
Selbst dann wenn es einzelne Ausnahmen geben würde, die keinerlei
Problem darstellen.
Bei
1
inta=5;
2
intb;
3
4
b=b=a;
mag ja noch das erwartete rauskommen. Aber spätestens bei
1
b=(b=a)+3;
ist der Ofen aus. 5 ist ein vollkommen zulässiges Ergebnis für b.
Man hätte den Normungstext so formulieren können, das Variante 1 als
Ausnahme zugelassen werden würde. Im Sinne der Allgemeinheit hat man es
aber gelassen, was ich prinzipiell gut finde. So gibt es eine einfache
Regel: 2 oder mehr versuchte Veränderungen an ein und derselben Variable
ist ein No-No. (innerhalb des nächsten Sequence Points. Aber die muss
man wieder wissen wo die sitzen. Glücklicherweise haben die meisten eine
intuitiv ziemlich korrekte Vorstellung davon, wo Sequence Points sind)
In C gibt es sowieso schon so ein paar dubiose Ausnahmeregelungen.
Manche davon historisch bedingt. Für das hier brauchts wirklich nicht
noch eine mehr. So wichtig ist das auch wieder nicht.
Jörg Wunsch schrieb:> Du darfst an etwas, was im Standard als “undefined” bezeichnet ist,> keinerlei Logik anwenden wie „bleibt ja trotzdem dasselbe“.
Ja. Die Sache ist eben auch, daß der Compiler mitunter eine andere Logik
anwendet und eben doch was anderes rausbekommt, weil man irgendeinen
Effekt nicht berücksichtigt hat. Für x = x++; hab ich auch tatsächlich
mal auf verschiedenen Zielplattformen (x86 und sparc) unterschiedliche
Ergebnisse rausbekommen.
Letztendlich heißt "undefined", daß der Compiler diesen Fall schlicht
und ergreifend gar nicht erst berücksichtigen muß bei seinen
Optimierungen und der daraus oft resultierenden Umsortierung des Codes.
Er muß sich nicht bemühen, ein Ergebnis rauszubekommen, daß jemand als
korrekt bezeichnen würde, denn bei undefiniertem Verhalten ist alles
korrekt, und das gilt nicht nur für das Ergebnis dieser einen Operation,
sondern für alles, was das Programm ab diesem Zeitpunkt tut.