Hallo!
Ich bin gerade etwas am verzweifeln mit C: Was passiert in folgendem
Code?
1
int16_tdiff;
2
int8_toffset=-20;
3
uint16_tadc=300;
4
diff=(512+offset)-adc;
Mein Gedanke wäre, dass in der Klammer (512+offset) der offset in
unsigned umgewandelt werden (bzw. vom Compiler als unsigned aufgefasst),
da 512 auch unsigned ist. Das Ergebnis wäre dann eine große positive
unsigned Zahl. Aber wie bekomme ich das mathematisch richtige Ergebnis,
wenn ich so einen typen-Salat habe?
Anonymous U. schrieb:> da 512 auch unsigned ist
Ist es nicht. Solange Du nur einen numerischen Wert hinschreibst, ist
der vorzeichenbehaftet.
Das kannst Du mit dem Suffix U ändern; schreib also nicht 512, sondern
512U.
Anonymous U. schrieb:> (bzw. vom Compiler als unsigned aufgefasst),> da 512 auch unsigned ist
das ist aber nicht so, wenn du willst das es unsigned ist dann schreibe
es hin.
(512u+offset)-adc;
Rufus Τ. F. schrieb:> Das kannst Du mit dem Suffix U ändern; schreib also nicht 512, sondern> 512U.
Ok, dann wäre die Klammer schonmal signed. Das heißt, ich muss dann auch
noch adc mit signed casten?
Frank M. schrieb:> Anonymous U. schrieb:>> Ok, dann wäre die Klammer schonmal signed.>> Das sollte reichen, damit auch der ganze Ausdruck signed wird.
Oh, entschuldige. Es könnten quasi auch negative Werte herauskommen:
Anonymous U. schrieb:> int16_t diff;> int8_t offset = -20;> uint16_t adc = 600;> diff = (512+offset)-adc;
diff = (512+(-20))-600 = -108
Das heißt, 512 passt als signed und ich muss adc als signed casten?
Wahnsinn wie solche Trivialitäten verwirren können :-/
Du musst erstmal schauen wie groß welche Typen sind und dann schauen was
passiert. Es wurde ja schon gesagt, dass 512 signed ist und der Typ ist
int. Deine Variablen sind aber vermutlich kleiner als int (nehme ich mal
an, int könnte auch int16_t sein). Unter diesen Voraussetzungen wird bei
512+offset die offset Variable zu einem int konvertiert. Das ist OK,
weil alle Zahlen eines int8_t lassen sich garantiert als int darstellen.
Dann soll dein adc Wert abgezogen werden und dieser ist uint16_t. Nach
meinen Voraussetzungen ist das kleiner als int also wird dies zu einem
int konvertiert da man auch alle Werte von uint16_t als int darstellen
kann. Am Ende kriegst du also einen int Wert von dem eventuell etwas
abgeschnitten wird wenn er nicht in ein int16_t passt.
Jetzt die andere Alternative wenn int16_t genauso groß ist wie ein int:
Dann haben wir bei der Subtraktion von adc die Typen int16_t und
uint16_t. In diesem Fall ist dein Ergebnis unsigned! Wenn also bei der
Berechnung etwas negatives rauskommt, dann kriegst du stattdessen eine
große postive Zahl. Hier was zum testen:
1
intx=INT_MAX;
2
unsignedinty=INT_MAX+1U;
3
// x-y hat Typ unsigned und kann damit nicht -1 sein
Bei Addition, Subtraktion und Multiplikation spielt es keine Rolle, ob
die Operanden signed oder unsigned sind.
Signed / Unsigned spielt nur eine Rolle
-- Für Extensions, also ob ein 8-Bit Wert, wenn er in eine 16-Bit
Berechnung eingeht, signed oder unsigned erweitert wird.
-- Bei Überlauf: Signed / Unsigned wird beim Overflow unterschiedlich
behandelt: Unsigned Überlauf wird modulo 2^Bitbreite gerechnet, Signed
Overflow ist Undefined Behavior.
Falls auch Signed Overflow definiert sein soll, geht das bei GCC mit
-fwrapv.
-- Bei saturierte Arithmetik (stdfix.h etc) spielt die Signedness eine
Rolle, z.B. wird für saturierte Signed- resp. Unsigned-Addition
unterschiedlicher Code erzeugt.
Wenn wir gerade dabei sind...
Es wird behauptet, der C Standard schreibe das Zweierkomplement nicht
vor. Der Compiler könne auch eine Kodierung nutzen, bei der Signed- und
Unsigned-Additionen unterschiedliche Ergebnisse liefern.
Stimmt das?
Noch einer schrieb:> Wenn wir gerade dabei sind...>> Es wird behauptet, der C Standard schreibe das Zweierkomplement nicht> vor. Der Compiler könne auch eine Kodierung nutzen, bei der Signed- und> Unsigned-Additionen unterschiedliche Ergebnisse liefern.>> Stimmt das?
Ja, das stimmt. Prinzipiell.
Allerdings waren derartige Maschinen schon damals die Ausnahme. Auf
aktuellen Maschinen wird praktisch nur 2-er Komplement verwendet.
Einfach weil es die beste Lösung ist.
Anonymous U. schrieb:> wenn ich so einen typen-Salat habe?
Zum Typen-Salat:
512: int
offset: wird int
(512 + offset): (int + int) => int
16-Bit: adc bleibt unsigned
(...)-adc: int-unsigned => unsigned
>16-Bit: adc wird zu int
(...)-adc: int-int => int
Der Typ vom Ergebnis hängt also von der Maschine ab.
Schonmal vielen Dank für die Erklärungen.
Sebastian V. schrieb:> Jetzt die andere Alternative wenn int16_t genauso groß ist wie ein int:> Dann haben wir bei der Subtraktion von adc die Typen int16_t und> uint16_t. In diesem Fall ist dein Ergebnis unsigned!
Ich programmier grad mit AVR-Studio einen Atmega (8bit). Also würde ich
meinen, dass der int Datentyp 8 Bit groß ist. Naja wie auch immer, ich
will auf jedenfall etwas definiertes. Also:
1
int16_tdiff;
2
int8_toffset=-20;
3
uint16_tadc=600;
4
diff=(512+offset)-(int16_t)adc;
So sollte ich ein signed als Ergebnis erhalten, egal ob int jez 16 oder
8 Bit breit ist.
Falls in AVR-Studio ein int wirklich 32 Bit breit wäre, so wäre das doch
Speicherverschwendung? Würde Folgendes dann mehr Sinn machen?
1
int16_tdiff;
2
int8_toffset=-20;
3
uint16_tadc=600;
4
diff=((int16_t)512+offset)-(int16_t)adc;
PS: Hintergrund ist, ich wollte den ADC auslesen. Dessen Wert will ich
in die Differenz von der Mitte (512) umrechnen. Dabei soll ein Offset um
die Mitte korrigiert werden (+-127). Demnach könnte ich ja als Offset
auch unsigned verwenden, und dann würde es halt heißen 385+offset-adc.
Mit signed ist der Code halt irgendwie verständlicher und schöner.
Ganz böse Falle - Im Gegensatz zum Standard rechnen die MC-Compiler mit
8Bit statt mit int. Solange du nicht explizit vor der Addition in int
castest.
Du musst aber an zwei Punkten höllisch aufpassen.
Wenn du "int = int8_t + int8_t" schreibst machen die meisten MC-Compiler
eine 8Bit Addition und erweitern erst danach auf 16 Bit.
In deinem Beispiel funktioniert zufälligerweise alles. 512 passt nicht
in 8 Bit und der Compiler erweitert beide Operanden auf 16 Bit.
Wie bei allen C Compilern sind die 8Bit->16Bit Umwandlungen für signed
und unsigned unterschiedlich.
Für die Addition ist egal, ob Operanden und Ergebnis signed oder
unsigned sind. Du addierst zwei Bitmuster und bekommst ein Bitmuster.
Bei einer Addition im Zweierkomplement ist egal, ob du diese Bitmuster
als signed oder unsigned betrachtest. int=int+int macht genau dasselbe
wie int=uint+uint. Kannst auch alles mit uint rechnen und erst beim
printf als %d ausgeben. Du bekommst das richtige Vorzeichen.
Solltest dir aber trotzdem angewöhnen, alles sauber als signed/unsigned
anzulegen. Mit 512 hat das Programm funktioniert, mit 127 spinnt es.
Solche Fehler sucht man ewig.
Und besser von vorn herein so anlegen, dass man nicht casten muss. Dann
brauchst du dir auch keine Gedanken machen, ob der Cast
uint8->int8->int16 oder uint8->uint16->int16 umwandelt.
Noch einer schrieb:> Wenn du "int = int8_t + int8_t" schreibst machen die meisten MC-Compiler> eine 8Bit Addition und erweitern erst danach auf 16 Bit.
Zumindest sollte man das im Auge behalten. Der hier recht beliebte
avr-gcc macht das allerdings nicht.
A. K. schrieb:> Noch einer schrieb:>> Wenn du "int = int8_t + int8_t" schreibst machen die meisten MC-Compiler>> eine 8Bit Addition und erweitern erst danach auf 16 Bit.>> Zumindest sollte man das im Auge behalten. Der hier recht beliebte> avr-gcc macht das allerdings nicht.
Und bei den Anderen handelt es sich dann nur um Compiler mit C-like
Syntax, denn die Integerpromotion ist etwas, was ein C-Compiler machen
muß. Zumindest solange man es per Cast nicht ausdrücklich anders
verlangt.
BTW, daß er sich an diese Regel hält, wird avr-gcc immer wieder
"vorgeworfen".
Noch einer schrieb:> Du musst aber an zwei Punkten höllisch aufpassen.>> Wenn du "int = int8_t + int8_t" schreibst machen die meisten MC-Compiler> eine 8Bit Addition und erweitern erst danach auf 16 Bit.
Das ist dann aber kein konformes C, also eigentlich ein Fehler. Der
Compiler darf sowas nur dann, wenn es am Ergebnis nichts ändert.
> Solltest dir aber trotzdem angewöhnen, alles sauber als signed/unsigned> anzulegen.
gcc warnt auch, wenn man signed und unsigned mischt. Wenn er das nicht
tut, sind die Warn-Einstellungen zu niedrig. Man sollte solches Mischen
auch grundsätzlich meiden und da, wo es sich nicht vermeiden läßt, genau
überlegen, was die Konsequenzen sind und dann mit Casts klar machen, wie
es gedacht ist.
Carl D. schrieb:> Zumindest solange man es per Cast nicht ausdrücklich anders verlangt.
Ein Cast ändert auch nichts. Auch (int8_t)100 + (int8_t)100 muß 200
ergeben, denn die Addition wird niemals mit was kleinerem gemacht als
int. Die Integer-Promotion wird durch den Cast nicht ausgehebelt, es
werden nach diesem beide Operanden erstmal wieder auf int erweitert,
bevor addiert wird.
Carl D. schrieb:> Und bei den Anderen handelt es sich dann nur um Compiler mit C-like> Syntax,
Hast du es auch eine Nummer kleiner? So streng verstanden ist avr-gcc
das auch, denn dessen "double" Datentyp entspricht nicht den Vorgaben
des Standards.
A. K. schrieb:> Rolf M. schrieb:>> Ein Cast ändert auch nichts. Auch (int8_t)100 + (int8_t)100 muß 200>> ergeben,>> (int8_t)(100 + 100)
Was willst du damit sagen? Auch so wird das 100 + 100 (zumindest formal)
in int gerechnet. Die Reduktion auf 8 Bit kommt erst danach und ginge
natürlich genauso auch ohne Cast:
Rolf M. schrieb:>> (int8_t)(100 + 100)>> Was willst du damit sagen? Auch so wird das 100 + 100 (zumindest formal)> in int gerechnet.
Ok, also
if ((uint8_t)(a + b) > 100)
Das darf der Compiler ohne Cast nicht in 8 Bits rechnen. Mit schon.
Rolf M. schrieb:> Die Reduktion auf 8 Bit kommt erst danach und ginge> natürlich genauso auch ohne Cast:> int8_t i = 100 + 100;
Laut Standrad erzeugt dies jedoch implementation-defined behavior (so
oder so, auch mit Cast):
> The result of, or the signal raised by, converting an integer to a> signed integer type when the value cannot be represented in an object of> that typeA. K. schrieb:> Ok, also> if ((uint8_t)(a + b) > 100)> Das darf der Compiler ohne Cast nicht in 8 Bits rechnen. Mit schon.
Bei unsigned ja. Da ist alles schön definiert und das Resultat ist in
beiden Fällen (Berechnung in 8 Bit vs. Berechnung in int und
anschliessender Cast) identisch. Bei signed sieht das aber anders aus.
Wird die Berechnung in 8 Bit durchgeführt, kann aufgrund eines
Überlaufes undefiniertes Verhalten auftreten. Rechnet er in int und
castet dann in einen kleineren signed Typen, muss das Resultat in den
kleineren Typen passen, da ansonsten implementierungsabhängiges
Verhalten auftritt. Jedenfalls kann dadurch das Ergebnis der beiden
Varianten nicht als identisch betrachtet werden. Der Compiler darf in
diesem Fall nicht optimieren sondern muss in int rechnen und
anschliessend casten.
A. K. schrieb:> Ok, also> if ((uint8_t)(a + b) > 100)> Das darf der Compiler ohne Cast nicht in 8 Bits rechnen. Mit schon.
Ja. Prinzipiell ist hier zwar auch weiterhin die integer promotion
durchzuführen, aber da es am Ergebnis nichts ändert, darf er als
Optimierung (entsprechend der "as-if rule") hier die Erweiterung auf
int auch weglassen und alles in 8 Bit rechnen.
be s. schrieb:> Wird die Berechnung in 8 Bit durchgeführt, kann aufgrund eines> Überlaufes undefiniertes Verhalten auftreten.
Dass im C Standard der Überlauf bei vorzeichenbehafteter Rechnung
undefiniert ist heisst nicht, dass es auch bei der realen Zielmaschine
undefiniert wäre. Das Verhalten der Zielmaschine ist dem Compiler
vielmehr bekannt. Er weiss also, dass bei einer 8-Bit Rechnung das
gleiche Ergebnis herauskommt wie bei einer nach allen Regeln des
Standards durchgeführten 16-Bit Rechnung.
> Der Compiler darf in diesem Fall nicht optimieren sondern muss in> int rechnen und anschliessend casten.
Doch, er darf. Und genau das tut er auch.
Anders wäre es beispielsweise, wenn die Zielmaschine beim Befehl zur
8-Bit Addition bei Überlauf einen Trap auslösen würde, beim Downcast mit
Überlauf aber immer 127 rauskäme. Dann wäre es nicht zulässig.