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
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...
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
*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)
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.
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
> 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.
-> 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
> 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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.