Forum: Compiler & IDEs Hilfe bei Codelesen


von Peter M. (peter9999)


Lesenswert?

Hallo,

ich verstehe nicht, was in der unteren Anweisung (work. ....) passiert, 
kann mir jemand auf die Sprünge helfen?

typedef struct  // ts_work
{
    uint8_t     OutputCIst;
    uint8_t     OutputCSoll;
    uint8_t     Output_Pulse;
    uint8_t     OW_maxSensors;
    uint8_t     OW_Bus_read;
    uint8_t     SDCard_Log_bit;
    uint8_t     Port_Automatik;
    uint8_t     Port_IdleState;
    uint8_t     Port_LEDcolor;
    uint8_t     DigIn_IdleState;
}  ts_work

  ts_work      work;
  uint8_t   uc_bit;

  work.Output_Pulse = work.Output_Pulse |= _BV(uc_bit);


Vielen Dank

peter

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Amateur (Gast)


Lesenswert?

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.

von PittyJ (Gast)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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
const uint8_t b = 0x01;
2
uint8_t a = 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.

von Rolf Magnus (Gast)


Lesenswert?

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
1
x = x++;

Auch das ergibt undefiniertes Verhalten.

von Rolf Magnus (Gast)


Lesenswert?

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.

von Peter M. (peter9999)


Lesenswert?

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

von PittyJ (Gast)


Lesenswert?

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.

von Fred (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Axel S. (a-za-z0-9)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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)
1
  double a, c;
2
  int b;
3
4
  c = 3.8;
5
6
  a = b = c;
a hat danach den Wert 3.0 und nicht etwa 3.8

: Bearbeitet durch User
von Rolf Magnus (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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“.

von Karl H. (kbuchegg)


Lesenswert?

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
  int a = 5;
2
  int b;
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.

: Bearbeitet durch User
von Rolf Magnus (Gast)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.