mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik C-Problem


Autor: Mike (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: inoffizieller WM-Rahul (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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...

Autor: Mike (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: inoffizieller WM-Rahul (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
*ptr++ = (*ptr <<4) + x;

könnte auch funktionieren (ungestestet)

Autor: Mike (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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)

Autor: yalu (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Maruchinu (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Karl heinz Buchegger (kbucheg)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Mike (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Karl heinz Buchegger (kbucheg)
Datum:

Bewertung
0 lesenswert
nicht 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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.