Hallo,
ich bin gerade etwas verwirrt.
Im EEPROM speichere ich an Adresse 0x08 einen Offsetwert. Dieser kann
von -128 bis 127 gehen. Daher interpretiere ich das gelesene Byte als
int8_t. Dessen Wert sollte laut
https://de.wikibooks.org/wiki/C-Programmierung_mit_AVR-GCC/_Datentypen
passen.
Die beiden EEPROM-Header sind inkludiert, weil ich der Arduino-Umsetzung
zunächst nicht getraut habe. Sie liefern aber beide das gleiche
Ergebnis.
Nun passiert aber folgendes:
Serial.println("Offset ist 0xFF und wird auf 0 gesetzt!");
19
offset=0;
20
}
21
else{
22
Serial.print("Offset ist nicht 0xFF sondern ");
23
Serial.println(offset);
24
}
25
}
26
27
voidloop(){
28
29
}
liefert die Ausgabe:
Size of offset: 1
FF
255
FF
FF
255
Offset ist 0xFF und wird auf 0x00 gesetzt!
Natürlich kann ich mir den Nullpunkt selber auf 127 legen und komme dann
auf das richtige Ergebnis. Aber ich verstehe nicht, warum der gcc dieses
Ergebnis liefert.
Könnt ihr mir da bitte helfen, oder zumindest sagen, wie ich auf die
richtige Spur komme?
Danke, Matthias
Es ist nicht nur das Serial, denn sonst wäre die if ja getriggert
worden.
Ich tippe mal darauf:
1
int8_toffset=0xFF;
2
uint8_tbyte=0xFF;
3
4
if(offset==byte)
Sprich: Der GCC sieht die 0xFF als Unsigned an (Das macht ja bei
Hexadezimal durchaus Sinn: Addressen und Co), und da eben zwar die Bits
übereinstimmen, aber die Zahlen dezimal unterschiedlich sind, haut es
nicht hin.
Oder noch schlimmer: Er macht aus deinem 0xFF zumindest ein 0x00FF und
shiftet das Vorzeichen, weil ja FF nicht in unsigned passt. Versuche mal
explizit "-1" anstatt 0xFF zu nehmen.
-1 ist übrigens die 2K-Darstellung von 0xFF
Die IF Bedingung kann bei int8_t nur fehlschlagen, da ein int8_t niemals
einen Wert von 255 erreichen kann. Wenn müsstest du auf -1 testen oder
aber den int8_t auf einen uint8_t casten für den Vergleich auf 0xFF.
Aber das nur am Rande.
>> if (offset == 0xFF)>> Das soll halt funktionieren :)
Nee das soll nicht unbedingt funktionieren.
Man kan doch auch nicht Volts mit Temperatur vergleichen oder Alter mit
Gewicht ?
offset ist int8
0xFF ist uint8
Gut wahre (nicht getested aber ich sehe da keine problemen) :
if (offset == (int8)0xFF) // Weil der Compiler dann beide als int8 sieht
oder
if ((uint8) offset == 0xFF) // Weil der Compiler dann beide als uint8
sieht
Mehr informationen >> Google auf 'typecasting'
Metti schrieb:> Mich stört nur das Ergebnis des Vergleiches. Ausgegeben wird> normalerweise nichts.>> if (offset == 0xFF)> offset = 0;>> Das soll halt funktionieren :)
offset ist int8_t
0xFF ist int16_t
Um den Vergleich zu machen muss der Kompiler offset zum int16_t
konvertieren.
Also muss der Vergleich (offset == 0xFF) IMMER falsch ergeben. Egal
welchen Wert offset annimmt.
Und damit ist der Vergleich, wie Thomas K. schon sagte völlig unsinnig.
Serial.println("Offset ist 0xFF und wird auf 0 gesetzt!");
3
offset=0;
4
}
5
else{
6
Serial.print("Offset ist nicht 0xFF sondern ");
7
Serial.println(offset);
8
}
9
}
funktioniert mit int8_t wie gewünscht.
Sowohl bei leerem EEPROM (dann wird auf 0 gesetzt) als auch bei realem
Wert:
Size of offset: 1
DD
221
DD
FFFFFFDD
-35
Offset ist nicht 0xFF sondern -35
So soll es sein. Ich habe nicht gewusst, dass die dezimale
Representation so stark "bindet". Ich habe immer gedacht, dass die
hexadezimale Schreibweise eindeutiger ist und ob jetzt -35 oder 221
draus gemacht wird, ist beim Vergleich egal. Beides ist 0xDD.
Danke & VG
Metti
Du hast noch immer nichts verstanden.
Dezimal und hexadezimal sind gleichwertig. Da "bindet" gar nichts.
FF ist 255. Immer.
Nur kann ein int8_t eben keine 255 aufnehmen. Eine -1 geht aber
natürlich.
Wie du auf die Idee kommst, -1 und 255 seien dasselbe, ist die viel
interessantere Frage.
Tim schrieb:> Wie du auf die Idee kommst, -1 und 255 seien dasselbe, ist die viel> interessantere Frage.
Weil sowohl int8_t = -1 und uint8_t = 255 mit 0xFF geschrieben
werden. Dass ein int8_t niemals grösser als 127 sein kann, hat
er wahrscheinlich übersehen, ist ja auch nicht sooo schlimm.
Bei int8_t bla = 0xFF ist FF aber nicht das Bitmuster des int8_t (und da
gehst du übrigens schonmal von Zweierkomplement aus, was praktisch der
Fall ist, aber keineswegs garantiert), sondern erstmal nur ein unsigned
int mit Wert dezimal 255.
Der wird dann implizit gecastet nach int8_t, was wiederum meistens -1
ergibt, aber das ist noch viel weniger garantiert. Signed overflow ist
undefined, und in letzter Zeit haben ein paar Leute auch an diversen
Stellen gemerkt, daß ihre Compiler da biestiger wurden als sie es
historisch waren. Mit Bitmusterdarstellung hat das nichts zu tun, auch
wenn es vom Ergebnis her so aussieht (und natürlich auch kein blanker
Zufall ist).
Mir ist schon klar, daß diese beiden falschen Vorstellungen verbreitet
sind. Sie sind aber nach wie vor falsch. Und wenn man verstehen will,
was passiert und nicht immer nur im Dunkeln tappen, muß man diese
Mechanismen verstehen.
Und deswegen ist dein flapsiges "-1 schreibt man als 0xFF" gefährlich.
Das kann man so sagen, wenn alle Beteiligten wissen, wovon sie sprechen.
95% der (auch langjährigen) C-Programmierer sind aber leider nicht auf
dem Niveau.
Tim schrieb:> Du hast noch immer nichts verstanden.>> Dezimal und hexadezimal sind gleichwertig. Da "bindet" gar nichts.>> FF ist 255. Immer.>> Nur kann ein int8_t eben keine 255 aufnehmen. Eine -1 geht aber> natürlich.>> Wie du auf die Idee kommst, -1 und 255 seien dasselbe, ist die viel> interessantere Frage.
Dass -1 und 255 nicht identisch sind, ist mir klar, sonst hätte ich
keine Geldsorgen mehr.
Ich will ja auch gar nicht, dass int8_t den Dezimalwert 255 annimmt. Ich
möchte ihn mit dem Inhalt einer 1 Byte großen Speicherzelle beschreiben,
dessen Wert 0xFF ist. Und ich habe nunmal angenommen, dass in jeden 8
Bit breiten Datentypen der Wert 0xFF reinpasst und sich dann auch
verarbeiten lässt. Meine Art der Verarbeitung war offenbar falsch. 2er
Komplement ist mir schon bekannt... Aber ich dachte eben, dass dies
lediglich bei der dezimalen Representation zum Tragen kommt und nicht,
wenn ich mit den echten Bits in Form von 0x arbeite. War offenbar
falsch, 0b, 0, Dec und Hex sind gleichwertige Schreibweisen habe ich nun
gelernt.
Danke.
Marcs Antwort kam, als ich den Hund gefüttert hab :-D
Danke nochmal für die Klarstellung.
Ich lese daraus, dass ich explizit sagen soll, wohin ich casten möchte,
damit nichts schief geht.
Probiere ich nachher nochmal aus.
Marc schrieb:> Der wird dann implizit gecastet nach int8_t,
Wird er eigentlich nicht (mal abgesehen davon, dass es keine "impliziten
Casts" gibt). Auch einen Überlauf gibt es hier nicht.
Stattdessen passiert hier die "Integer promotion". Bei dem Vergleich
werden, wie an sehr vielen Stellen, alle Integer, die kleiner als int
sind, erstmal auf int erweitert, also auf dem AVR auf 16 Bit. Und dort
sind eben auch im Zweierkomplement die Bitpatterns für -1 und für 255
nicht gleich.
Matthias M. schrieb:> Ich lese daraus, dass ich explizit sagen soll, wohin ich casten möchte,> damit nichts schief geht.
Besser wäre es, bei Rechenoperationen und Vergleichen vorzeichenlose und
vorzeichenbehaftete Typen möglichst nicht zu mischen.
Matthias M. schrieb:> Komplement ist mir schon bekannt... Aber ich dachte eben, dass dies> lediglich bei der dezimalen Representation zum Tragen kommt und nicht,> wenn ich mit den echten Bits in Form von 0x arbeite. War offenbar> falsch, 0b, 0, Dec und Hex sind gleichwertige Schreibweisen habe ich nun> gelernt.
Lass dich nicht entmutigen, du liegst gar nicht so falsch.
1
uint8_tub=0;
2
3
ub=-15;
4
ub=241;
Wird klaglos übersetzt, mit 0xF1 als Wert.
1
uint8_tub;
2
int16_tsi;
3
4
si=0;
5
ub=-15;
6
si+=ub;
7
8
si=0;
9
ub=241;
10
si+=ub;
Und dann siehst du, dass sogar GCC deiner Meinung ist ;)
Wenn du eine Speicherzelle mit dem Wert 0xFF in eine Variable vom Typ
int8_t einliest, dann erhälst du den Wert -1.
Wenn du eine Speicherzelle mit dem Wert 0xFF in eine Variable vom Typ
*u*int8_t einliest, dann erhälst du den Wert 255.
Dementsprechend musst du mit -1 bzw. 255 vergleichen.
Deine Schreibweise von 0xFF im Quelltext ist lediglich eine alternative
Syntax. 0xFF entspricht 255, das wird Dir jeder Mathelehrer bestätitgen.
Der Compiler würde niemals deine 0xFF im Quelltext nach -1 übersetzen,
denn der Compiler hält sich soweit möglich an die Regeln der Mathematik.
Oder genau gesagt, an die C Spezifikation.
C wurde designed, um Plattformübergreifend zu programmieren. Also wurde
ganz bewusst vermieden, -1 als 255 zu behandeln. Denn das würde nur auf
bestimmte CPU's zutreffen. Mittlerweilen sind das zwar IMHO alle, das
das war nicht immer so und könnte sich im Zukunft auch wieder ändern.
Stefan U. schrieb:> C wurde designed, um Plattformübergreifend zu programmieren. Also wurde> ganz bewusst vermieden, -1 als 255 zu behandeln. Denn das würde nur auf> bestimmte CPU's zutreffen. Mittlerweilen sind das zwar IMHO alle, das> das war nicht immer so und könnte sich im Zukunft auch wieder ändern.
Hm, wann war denn das? Also dass z.B. bei einem int8 eine 255 nicht als
-1 behandelt wurde? Das ergibt sich doch automatisch aus dem Binärcode
von 255 dass das bei einem int8 der -1 entspricht.
chris_ schrieb:> Wo ist die Spezifikation für den Wert in k:
Du meinst warum der 0xff ist zu beginn? Na, das steht im Datenblatt des
Atmegas: Wird das eeprom gelöscht steht an jeder Speicherstelle ein
0xff. Das hat mit der internen (invertierenden) Logik der
Speicherbausteine zu tun ;)
M. K. schrieb:> Hm, wann war denn das? Also dass z.B. bei einem int8 eine 255 nicht als> -1 behandelt wurde? Das ergibt sich doch automatisch aus dem Binärcode> von 255 dass das bei einem int8 der -1 entspricht.
Nein.
Das ist bei der Zweier-Komplement-Darstellung so.
Die muss aber nicht verwendet werden. Solche Systeme gab es mal.
Wenn das Programm aber nur auf einem ATMega mit Zweier-Komplement läuft,
braucht man darauf aber keine Rücksicht nehmen.
Dirk B. schrieb:> Solche Systeme gab es mal.
Keine Frage, die gabs und gibts sicher immer noch.
Dirk B. schrieb:> Wenn das Programm aber nur auf einem ATMega mit Zweier-Komplement läuft
Ja, das war im Prinzip meine Frage: Wann war das auf dem Atmega denn mal
anders? Muss lange her sein.
>Hat jemand vielleicht einen Link auf die Spezifikation der>Cast-Behandlung?>>Du meinst warum der 0xff ist zu beginn? Na, das steht im Datenblatt des>>Atmegas:
Nein, mich interessiert die prozessorunabhängige Spezifikation von "C".
chris_ schrieb:> Nein, mich interessiert die prozessorunabhängige Spezifikation von "C".
Das ist nicht spezifiziert. Nach C wird mit der Deklaration
1
uint8_tmyUInt8;
myUInt8 ein Speicherbereich vom RAM zugewiesen. In myUInt8 steht dann
das drin, was an dem Speicherbereich steht. Das kann 0 sein, das kann 23
sein, im Fall des EEPROMs im Atmega steht da 0xFF drin, also -1 (wegen
der schon genannten Zweierkomplementdarstellung im Atmega).
Erst mit
1
myUInt8=0xff;
wird myUInt8 definiert auf 0xff gesetzt. Man kann natürlich Deklaration
und Definition in eine Zeile packen in C damit das dann so aussieht:
>> Das ist bei der Zweier-Komplement-Darstellung so.> Wann war das auf dem Atmega denn mal anders?
Noch nie.
Aber du verdrehst meine Aussage.
Ich habe gesagt, dass C plattformübergreifend designed wurde. Also eben
nicht nur für AVR. Das trifft sogar ganz besonders auf den avr-gcc zu,
der vom gcc abgeleitet wurde, der wiederum für zahlreiche Architekturen
verwendet wird und auch dafür gedacht war.
Es ist meist eine gute Idee den gcc mit -Wconversion zu nutzen.
1
> gcc -Wconversion -c test1.c
2
test1.c:2:1: warning: conversion to ‘int8_t’ alters ‘int’ constant value [-Wconversion]
3
test1.c:3:1: warning: negative integer implicitly converted to unsigned type [-Wsign-conversion]
Wer meinte hier, dass die Darstellung der Literale (ob Dezimal oder Hex)
keine Rolle spielt? Manchmal schon... (amd64)
1
#include<stdint.h>
2
inti=0xFFFFFFFF;
3
intj=4294967295;
Hex ist hier unsigned. Dezimal dagegen long int.
1
> gcc -Wconversion -c test2.c
2
test2.c:2:1: warning: conversion of unsigned constant value to negative integer [-Wsign-conversion]
3
test2.c:3:1: warning: conversion to ‘int’ alters ‘long int’ constant value [-Wconversion]
Ich gerade eine Seite gesucht, die übersichtlich die Typen, die
Fixed-width Types, Literale, Conversion und Promotion beschreibt, aber
leider nix passendes gefunden. Vielleicht kennt ihr ja eine gute
Übersicht (und ich meine nicht ANSI X3.159-1989 oder ISO/IEC 9899 ;-)
Stefan U. schrieb:> Aber du verdrehst meine Aussage.
Hab ich das gemacht? Das wollte ich natürlich nicht, entschuldige.
Stefan U. schrieb:> Ich habe gesagt, dass C plattformübergreifend designed wurde. Also eben> nicht nur für AVR.
Dagegen hab ich ja auch nix gesagt. Hier gehts aber nunmal in Bezug auf
AVRs also sollte man auch dabei bleiben finde ich. Man kann auch groß
ausholen aber das würde sicher den Rahmen sprengen, den uns dieses Forum
bieten will.
M. K. schrieb:> Hm, wann war denn das? Also dass z.B. bei einem int8 eine 255 nicht als> -1 behandelt wurde? Das ergibt sich doch automatisch aus dem Binärcode> von 255 dass das bei einem int8 der -1 entspricht.
Nur wenn man das Zweierkomplement benutzt. C läßt aber auch das
Einerkomplement zu. Da entspricht die -1 der 254, oder
sign/magnitude-Darstellung. Da würde die -1 der 129 entsprechen.
chris_ schrieb:> Hat jemand vielleicht einen Link auf die Spezifikation der> Cast-Behandlung?>> Wo ist die Spezifikation für den Wert in k:> int8_t k=0xFF;
Das hat mit Casts nichts zu tun. 0xFF ist vom Typ int. Die unteren 8 Bit
dieses int werden nach k kopiert. Der Wert von k ist dann derjenige, der
rauskommt, wenn man 8 gesetzte Bits als int8_t interpretiert. Welcher
das ist, ist implementationsabhängig.
Mikro 7. schrieb:> Wer meinte hier, dass die Darstellung der Literale (ob Dezimal oder Hex)> keine Rolle spielt? Manchmal schon... (amd64)> #include <stdint.h>> int i = 0xFFFFFFFF ;> int j = 4294967295 ;>> Hex ist hier unsigned. Dezimal dagegen long int.
Ja, die Regeln, nach denen bei Integerkonstanten die Typen ausgewählt
werden, sind für Hex und Dezimal leider unterschiedlich.
Bei dezimalen Konstanten ohne Suffix ist die Reihenfolge int, long, long
long. Der für die Konstante verwendete Typ ist dann in dieser Reihe der
erste, in den der Wert reinpasst. Bei hexadezimalen Konstanten heißt die
Reihe dagegen int, unsigned int, long, unsigned long, long long,
unsigned long long.
Rolf M. schrieb:> M. K. schrieb:>> Wo ist die Spezifikation für den Wert in k:>> int8_t k=0xFF;>> Das hat mit Casts nichts zu tun. 0xFF ist vom Typ int.
0xff ist unsigned.
> Die unteren 8 Bit dieses int werden nach k kopiert.
Das Ergebnis ist implementation defined.
Johann L. schrieb:> 0xff ist unsigned.
LOL.
Bestimmt nicht, aber das hängt einzig und alleine vom Compiler ab.
Wie der Compiler diesen Wert interpretiert, wird bei der definition
der Variable festgelegt.
Marc V. schrieb:> Johann L. schrieb:>> 0xff ist unsigned.>> LOL.> Bestimmt nicht,
Ja, erst wenn die Konstante groß genug ist: 0x8000 ist z.B. unsigned
während 32768 signed ist.
> Was wird da geladen, -1 oder 255?
Das kann aus dem Assembler-Code nicht entnommen werden. avr-as frisst
übrigens genauso "ldi *, -1" ohne dass du einen unterschied merken
wirst. Es kann sich aber auch um einen fixed-point wert wie -0.008hr
handeln (short _Fract) der intern so dargestellt wird.
Johann L. schrieb:> ...bevor ich da noch weiter rumeiere :-)
und ich auch ;)
Es ging nur darum, ob man alleine aus dem Wert 0xFF sagen kann
"das ist signed oder das ist unsigned".
Kann man aber nicht.
Alles andere was du angeführt hast ist richtig und ich stimme
vollkommen mit dir überein.
Johann L. schrieb:> Marc V. schrieb:>>> ldi r16, 0xFF>> > Was wird da geladen, -1 oder 255?>> Das kann aus dem Assembler-Code nicht entnommen werden. avr-as frisst> übrigens genauso "ldi *, -1" ohne dass du einen unterschied merken> wirst. Es kann sich aber auch um einen fixed-point wert wie -0.008hr> handeln (short _Fract) der intern so dargestellt wird.
Genau da liegt der Unterschied in dieser Hinsicht zwischen C und
Assembler. Letzterer kennt keine signed- oder unsigned-Datentypen. Was
es ist, hängt einfach davon ab, wie du damit arbeitest und als was du es
als Programmierer interpretierst. Dem Assembler selbst ist das völlig
wurscht. Der C-Compiler behandelt signed und unsigned dagegen
unterschiedlich.
M. K. schrieb:> Stefan U. schrieb:>> C wurde designed, um Plattformübergreifend zu programmieren. Also wurde>> ganz bewusst vermieden, -1 als 255 zu behandeln. Denn das würde nur auf>> bestimmte CPU's zutreffen. Mittlerweilen sind das zwar IMHO alle, das>> das war nicht immer so und könnte sich im Zukunft auch wieder ändern.>> Hm, wann war denn das? Also dass z.B. bei einem int8 eine 255 nicht als> -1 behandelt wurde? Das ergibt sich doch automatisch aus dem Binärcode> von 255 dass das bei einem int8 der -1 entspricht.
Über Wikipedia (Einerkomplement) zu
http://www.fourmilab.ch/documents/univac/minuszero.html gekommen: die
Univac 1100er waren offenbar solche Kisten.
Ralf D. schrieb:> Über Wikipedia (Einerkomplement) zu> http://www.fourmilab.ch/documents/univac/minuszero.html gekommen: die> Univac 1100er waren offenbar solche Kisten.M. K. schrieb:> Ja, das war im Prinzip meine Frage: Wann war das auf dem Atmega denn mal> anders? Muss lange her sein.
Am besten immer erst mal den Thread fertig lesen. Klar gibt es
verschiedene Darstellungen aber ich meinte damit wann das auf dem Atmega
mal anders war ;)
if (offset == 0xFF) {
Du gehst fälschlicherweise davon aus (und genau darin liegt der Grund
Deiner Verwirrung) daß zuerst die rechte Seite in das Datenformat der
linken Seite gepresst wird (somit aus der 255 eine -1 wird) und dann
erst der Vergleich durchgeführt wird.
Aber dem ist nicht so!
Gemäß C-Standard werden beide Seiten erstmal in ein int konvertiert
und dann erst wird der Vergleich durchgeführt, also wird aus der
linken Seite ein (int)-1 und aus der rechten Seite ein (int)255 und das
ist ganz offensichtlich ungleich!
Du willst es lieber so schreiben wenn es funktionieren soll:
if (offset == -1) {
oder meinetwegen (unschön) auch
if (offset == (int8_t)0xff) {
oder um dem ganzen die Krone der Obskurität aufzusetzten castest Du das
linke int8_t zuerst in ein uint8_t so daß es bei -1 den Wert 255
annimmt:
if ((uint8_t)offset == 0xff) {
---
Da es jedoch wohl einen Grund gibt warum offset hier ausdrücklich als
als signed definiert wurde, offenbar weil es wohl je nach Anwendung
manchmal durchaus in der Natur der Sache liegt daß sowas wie ein
"offset" auch mal negative Werte annehmen kann ist es einfacher zu lesen
und zu verstehen wenn man negative Zahlen auch ganz unverblümt als
negative Zahlen hinschreibt (also mit Vorzeichen und nicht etwa indirekt
verklausuliert über wackelige Annahmen interner Zahlenrepräsentationen,
Überläufe und Zweierkomplemente) und somit wäre der erste Vorschlag
if (offset == -1)
wohl mit Abstand der vernünftigste Weg.
M. K. schrieb:> Ralf D. schrieb:>> Über Wikipedia (Einerkomplement) zu>> http://www.fourmilab.ch/documents/univac/minuszero.html gekommen: die>> Univac 1100er waren offenbar solche Kisten.>> M. K. schrieb:>> Ja, das war im Prinzip meine Frage: Wann war das auf dem Atmega denn mal>> anders? Muss lange her sein.>> Am besten immer erst mal den Thread fertig lesen. Klar gibt es> verschiedene Darstellungen aber ich meinte damit wann das auf dem Atmega> mal anders war ;)
Das war halt nicht nur auf dem Atmega lange her, sondern ganz allegmein
schon so einige Jahrzehnte. ;-)
Ansonsten kannst du auch auf dem Atmega 1er-Komplement benutzen (man muß
halt selbst für End-Around-Carry sorgen), für Divisionen könnte das
praktisch sein. Prinzipiell auch nicht anders als z.B.
Festkommaarithmetik zu benutzen.