Forum: Mikrocontroller und Digitale Elektronik C-Problem


von Mike (Gast)


Lesenswert?

Hallo,

obwohl ich schon seit Jahren in C programmiere, knabbere ich gerade an
einem mir unverständlichen Problem.

Es geht um ein Programm, das einzelne Nibbles von einer seriellen
Schnittstelle eines Microcontrollers liest und zu ganzen Bytes
zusammensetzen und in einem Array speichern soll. Das (vereinfachte)
Programm sieht etwa so aus:

unsigned char *ptr;
unsigned char c,x;
unsigned char dataField[256];
ptr = dataField;
.
.
c= getc(); //hole ein ASCII-Zeichen
x=ctox(c); //wandle um in HEX-Nibbel (0..0xF)
*ptr = x;  //speichere oberen Nibbel
x= getc(); //hole nächstes Zeichen
x=ctox(c); //wandle um
*ptr = (*ptr++ <<4) + x; //baue Byte zusammen, speichere und inc ptr
.
.

Der letzte Block wird solange wiederholt, wie Zeichen vorhanden sind.
Im Ergebnis finden sich leider nicht die Bytes wie gewünscht, sondern
nur die oberen Nibbles, jedoch auf den unteren 4 bits und jeweils ein
Platz zu hoch. Der Fehler muss irgendwo bei der Pointerinkrementierung
in der letzten Zeile liegen, denn beim Durchsteppen ist bis dahin alles
wie es sein soll. Dennoch scheint mir die Syntax der Zeile richtig zu
sein. Wenn ich mich richtig erinnere, bedeutet das Postinkrement
jedoch, dass erst alle Anweisungen der Zeile ausgeführt werden und dann
der Pointer um eins erhöht wird, oder irre ich mich da?

Ich bin mit meinem C-Latein am Ende. Weiss jemand, woran es liegen
könnte?

Danke Mike

von inoffizieller WM-Rahul (Gast)


Lesenswert?

Zur Fehlersuche:
Nimm die Pointer-Bearbeitung doch mal aus der Berechnung heraus.
Oder teile die komplette Zeile in einzelne Schritte (Anweisungen) auf,
wie sie eigentlich arbeiten soll.

So wie ich die die Zeile verstehe, wird der Pointer erst erhöht und
dann x hinzuaddiert; x also in die nächste Zelle geschrieben.
Dazu muß ich vielleicht noch sagen, dass mir Pointer-Operationen immer
noch etwas suspekt sind...

von Mike (Gast)


Lesenswert?

Ich habe mal die Pointerinkrementierung aus der Anweisung rausgezogen:

Statt

*ptr = (*ptr++ <<4) + x;

nun:

*ptr = (*ptr <<4) + x;
ptr++;

Jetzt funktionierts korrekt. Merkwürdig, ich hätte dennoch immer
gedacht, dass beide Schreibweisen äquivalent sein sollten. Der Compiler
scheint aber zuerst die rechte Seite auszuwerten, dann den Pointer zu
erhöhen und zuletzt erst die Zuweisung durchzuführen, wodurch das
Ergebnis eine Etage zu hoch landet.

Gruss
Mike

von inoffizieller WM-Rahul (Gast)


Lesenswert?

*ptr++ = (*ptr <<4) + x;

könnte auch funktionieren (ungestestet)

von Mike (Gast)


Lesenswert?

*ptr++ = (*ptr <<4) + x;
habe ich  auch probiert, geht leider auch nicht. Eine Analyse des vom
Compiler generierten Assemblercodes (Kommentare von mir zugefügt) zeigt
auch warum:
Erst wird der pointer erhöht, dann die rechte Seite ausgewertet, was
eindeutig den C-Regeln widerspricht. Anschliessend wird das Ergebnis an
die alte Stelle (vor der Inkrementierung) zurückgespeichert.

Merkwürdig, merkwürdig!

Korrekt:

;## # C_SRC :                       *ptr = (*ptr <<4) + x;
  mov.w  -11[FB],A0  ;  ptr -> A0
  mov.b  [A0],R0H        ;  *ptr -> R0H
  shl.b  #04H,R0H        ;  schiebe 4 nach links
  add.b  R0H,R0L         ;  addiere x dazu
  ._eblock
  mov.b  R0L,[A0]        ; Erbegnis nach *ptr
  ._line  447
;## # C_SRC :                       ptr++;
  add.w  #0001H,-11[FB]  ;  ptr +1 -> ptr

Inkorrekt:

;## # C_SRC :                       *ptr++ = (*ptr <<4) + x;
  mov.w  -11[FB],A0  ;   ptr -> A0
  add.w  #0001H,-11[FB]  ;   ptr++
  mov.w  -11[FB],A1  ;   ptr ->A1
  mov.b  [A1],R0H        ;   *A1 -> R0H
  shl.b  #04H,R0H        ;   schiebe R0H nach links
  add.b  R0H,R0L         ;   addiere x dazu
  ._eblock
  mov.b  R0L,[A0]        ;  -> *ptr (vor der Inkrementierung)

von yalu (Gast)


Lesenswert?

Das Verhalten widerspricht nicht den C-Regeln.

Der Ausdruck var++ liefert den Inhalt von var und inkrementiert
danach. "Danach" kann heißen sofort danach oder auch später, aber
immer noch innerhalb des aktuellen Gesamtausdrucks. Taucht var in
einem
Ausdruck mehrmals auf, ist das Ergebnis undefiniert, da keine feste
Reihenfolge bei der Auswertung der Teilausdrücke vorgeschrieben ist.

Also: var++ (u.ä.) nur dann verwenden, wenn var genau einmal im
Gesmtausdruck vorkommt. In

*ptr = (*ptr <<4) + x; ptr++;

ist dies gegeben, in

*ptr = (*ptr++ <<4) + x;

nicht.

von Maruchinu (Gast)


Lesenswert?

Hallo erstmal !

Hab nur was bemerkt, vielleicht liegts auch daran

c= getc(); //hole ein ASCII-Zeichen
x=ctox(c); //wandle um in HEX-Nibbel (0..0xF)
*ptr = x;  //speichere oberen Nibbel

als nächstes holst du dir das nächste zeichen....

x= getc(); //hole nächstes Zeichen

aber wandelst das alte um :-/

x=ctox(c); //wandle um
*ptr = (*ptr++ <<4) + x; //baue Byte zusammen, speichere und inc ptr

von Karl heinz B. (kbucheg)


Lesenswert?

> Erst wird der pointer erhöht, dann die rechte Seite ausgewertet,
> was eindeutig den C-Regeln widerspricht. Anschliessend wird das
> Ergebnis an die alte Stelle (vor der Inkrementierung)
> zurückgespeichert.
>
> Merkwürdig, merkwürdig!

Das einzige was hier den C-Regeln widerspricht, ist der
Source-Code.

*ptr = (*ptr++ <<4) + x;

Hat undefiniertes Verhalten, da *ptr in diesem Ausdruck
2 mal verändert wird (einmal über den ++ und einmal über
die Zuweisung), ohne dass ein Sequenzpunkt dazwischen wäre.
Das ist eine klare Verletzung der C-Regeln. Und zwar vom
Programmierer und nicht vom Compiler.

von Mike (Gast)


Lesenswert?

-> Maruchinu
Danke für den Hinweis, da hat sich ein Tippfehler eingeschlichen, im
echten Programm steht natürlich zweimal c = getc(). Sonst hätte die
zweizeilige Zuweisung auch nicht funktioniert.

-> kbucheg
In dem Ausdruck wird *ptr nicht zweimal verändert, da ++ nach den
C-Prioritätsregeln auf ptr wirkt und nicht auf den vom Pointer
addressierten Speicherbereich. Verboten wäre dagegen zweimal *ptr++ im
selben Ausdruck.

Inzwischen hat sich das Problem aber weitgehend geklärt:

Ich habe mal in der C-Referenz von Ritchie (einem der Autoren der
C-Programmiersprache) nachgeschaut. Da steht drin, dass bei Postfix
Increment zuerst der betreffende ausdruck (lvalue) ausgewertet wird und
dann der Pointer erhöht. Der lvalue ist im obigen Beispiel die Seite
links bzw. rechts vom Gleichheitszeichen, nicht die komplette Zeile.
Insofern hat der Compiler recht, auch wenn es nicht der Erwartung
entspricht.  Er hätte ja wenigstens eine Warnung ausgeben können...
Man sollte also eine Variable, die man inkrementiert nicht rechts und
links vom = verwenden. Leider steht es auch falsch in meinem Lehrbuch,
wo i= i++ + 1; als Demobeispiel steht.

Gruss an alle
Mike

von Karl heinz B. (kbucheg)


Lesenswert?

> In dem Ausdruck wird *ptr nicht zweimal verändert

Du hast Recht (schäm).

Das ändert aber nichts. Der gesamte Ausdruck hat immer noch
undefiniertes Verhalten. Wenn auch aus einem anderen Grund :-)

Der Grund ist darin zu suchen, dass in C nicht definiert ist
in welcher Reihenfolge der linke bzw. rechte Teil einer Zuweisung
ausgewertet werden. Das Ganze hat eigentlich nichts mit dem ++
an sich zu tun, sondern einfach nur damit, dass eine Variable
im rvalue (also dem rechten Teil der Zuweisung) verändert wird,
die gleichzeitig im lvalue der Zuweisung benutzt wird.

In C sind viele Dinge die auf Reihenfolgen beruhen, nicht definiert.
Ich denke da zb an Argumentlisten. Es ist nicht definiert in welcher
Reihenfolge eine Argumentliste anzuarbeiten ist: von Links nach Rechts
oder von Rechts nach Links oder überhaupt in einer komplett anderen
Reihenfolge. In

  foo( Fnct1(), Fnct2() );

ist also nicht definiert ob zuerst Fnct1() und erst dann Fnct2()
aufgerufen werden soll oder umgekehrt. Solange Fnct1() nicht
von Fnct2() abhängt, oder die Abhängigkeit andersrum besteht,
spielt das auch keine Rolle.

> Leider steht es auch falsch in meinem Lehrbuch,
> wo i= i++ + 1; als Demobeispiel steht

Du würdest dich wundern wieviele Fehler, durchaus auch schwere
Fehler, in Lehrbüchern zu finden sind. Noch schlimmer sind nur
noch Uni-Dozenten die ein Semester lang C unterichten.

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.