Hallo Freunde,
ich bau mir gerade ein printf() für meinen dsPIC33 (C30-Compiler)
zusammen und dabei ist mir etwas aufgefallen.
Wenn ich die Funktion mit Variablen aufrufe, ist alles bestens:
1
sint16_ts16=-32768;
2
meinPrintf("%i",s16);
Wenn ich die Funktion mit Konstanten aufrufe, sieht es anders aus:
1
meinPrintf("%i",-32768);
Dann passiert folgendes:
Alle Werte zwischen -32767 (0x8001) und 32767 (0x7FFF) werden als
16Bit-Wert übergeben und alles funktioniert.
Der Wert -32768 (0x8000) jedoch wird als vorzeichenerweiterter
32-Bit-Wert (0xFFFF8000) übergeben (und mein printf() bekommt das 0xFFFF
und zeigt -1 an).
Was ich daran nicht verstehe:
Wieso wird denn ausschließlich der eine Wert -32768 so erweitert?
Ist da ein geheimes System dahinter?
Bronco schrieb:> Wieso wird denn ausschließlich der eine Wert -32768 so erweitert?> Ist da ein geheimes System dahinter?
Scheint mir ein Fehler im Compiler zu sein.
Benachrichtige den Compilerhersteller, dass seine Datentypzuweisung im
lexikalischen Parser bei Konstanten mit negativem Vorzeichen an der
Grenze von int zu long fehlerhaft ist.
Die Konstante 32768 ist ein long int, da sie nicht als unsigned gekenn-
zeichnet und somit nicht mit 16 Bit darstellbar ist. Die Negation eines
long int ist wieder ein long int, und dieses wird an meinPrintf überge-
ben. Der Compiler macht das schon richtig, denn das Negationszeichen ist
nicht Bestandteil des Integer-Literals.
Abhilfe:
Bronco schrieb:> jedoch wird als vorzeichenerweiterter> 32-Bit-Wert (0xFFFF8000) übergeben (und mein printf() bekommt das 0xFFFF> und zeigt -1 an).
Bleibt noch zu klären, warum dein printf 0xfff bekommt, und nicht
0x8000.
Oliver
Oliver schrieb:> Bleibt noch zu klären, warum dein printf 0xfff bekommt, und nicht> 0x8000.
weil es sich hier offensichtlich um eine Little-Endian-Maschine handelt.
Yalu X. schrieb:> Die Konstante 32768 ist ein long int, da sie nicht als unsigned gekenn-> zeichnet und somit nicht mit 16 Bit darstellbar ist. Die Negation eines> long int ist wieder ein long int, und dieses wird an meinPrintf überge-> ben. Der Compiler macht das schon richtig, denn das Negationszeichen ist> nicht Bestandteil des Integer-Literals.
Bist du dir da sicher?
Meiner Meinung nach müsste -32768 noch als 16 Bit signed integer
durchgehen und das - an dieser Stelle Teil des Literals sein. Ansonsten
gäbe das in weiterer Folge mächtig Ärger bei Ausdrücken wie
3 * -5
Aber ich kann mich irren.
Karl Heinz Buchegger schrieb
> Benachrichtige den Compilerhersteller,
Nur zur Info: der C30 (der inzwischen XC16 heißt), meldet sich als gcc
> gcc version 4.0.3 (dsPIC30, Microchip v3_30)
MfG Klaus
> Meiner Meinung nach müsste -32768 noch als 16 Bit signed integer> durchgehen und das - an dieser Stelle Teil des Literals sein. Ansonsten> gäbe das in weiterer Folge mächtig Ärger bei Ausdrücken wie> 3 * -5> Aber ich kann mich irren.
Das unäre - ist ein Operator wie jeder andere, man kann ihn nicht nur
auf Literale anwenden.
1
a=-b;
oder auch
1
a=3*-b;
geht ja auch. Da es keinen Identifier -b gibt, kann er nicht Bestandteil
von dem, was folgt, sein.
der mechatroniker schrieb:>> Meiner Meinung nach müsste -32768 noch als 16 Bit signed integer>> durchgehen und das - an dieser Stelle Teil des Literals sein. Ansonsten>> gäbe das in weiterer Folge mächtig Ärger bei Ausdrücken wie>> 3 * -5>>> Aber ich kann mich irren.>> Das unäre - ist ein Operator wie jeder andere, man kann ihn nicht nur> auf Literale anwenden.>>
alles klar. Damit kann ein 'integer constant' schon mal kein Vorzeichen
haben.
1
6.4.4.5
2
The type of an integer constant is the first of the corresponding
3
list in which its value can be represented.
Und damit ist auch klar, dass eine 16-Bit int Konstante nur von 0 bis
maximal 32767 gehen kann. Und damit muss -32768 ein long sein, weil
32768 kein 16 Bit int sein kann und viel wichtiger -32768 ist nicht
einfach nur eine int-Konstante, sondern die Konstante lautet auf 32768,
auf die die Operation 'uniäres Minus' angewendet wird.
Hab ich mir noch nie genauer angesehen. Wieder was gelernt.
Karl Heinz Buchegger schrieb:> Hab ich mir noch nie genauer angesehen. Wieder was gelernt.
Ich war mir erst auch nicht sicher, hatte aber auf Broncos Frage hin
einen Verdacht und habe dann ebenfalls im Standard nachgeschaut :)
Wie oft benutzt man schon INT_MIN als Dezimalzahl ausgeschrieben?
Yalu X. schrieb:> Wie oft benutzt man schon INT_MIN als Dezimalzahl ausgeschrieben?
Hmm.
Woha. Da öffnet sich ja ein halber Sack von Würmern.
Wobei ja aber auch eigentlich
#define INT_MIN (-32767 - 1)
nicht verboten ist.
1
the following shall be replaced by expressions that have the same type
2
as would an expression that is an object of the corresponding type
3
converted according to the integer promotions. Their implementation-defined
4
values shall be equal or greater in magnitude to those shown, with the
5
same sign.
Und ein int-Objekt kann ja definitiv den Wert -32768 annehmen. Nur als
Literal lässt sich das nicht hinschreiben. :-)
Edit:
Oder hab ich dich jetzt missverstanden und du wolltest genau darauf
hinaus :-)
Noch ein Link. Wie zu erwarten sind wir nicht die ersten, die da
drüberstolpern.
http://www.hardtoc.com/archives/119
Ist es nicht faszinierend. Da programmierst du ein halbes Leben in einer
Sprache und kommst immer wieder auf Dinge drauf, die ganz anders sind
als der erste Gedanke der dir dazu durch den Kopf schiesst.
Karl Heinz Buchegger schrieb:> Und ein int-Objekt kann ja definitiv den Wert -32768 annehmen. Nur als> Literal lässt sich das nicht hinschreiben. :-)
In C wohl nicht, aber in Pascal:
1
programsint;
2
3
VAR
4
b,
5
sint:integer;// hier 16bit (Wertebereich: -32768..+32767)
Yalu X. schrieb:> Wie oft benutzt man schon INT_MIN als Dezimalzahl ausgeschrieben?Karl Heinz Buchegger schrieb:> Und ein int-Objekt kann ja definitiv den Wert -32768 annehmen. Nur als> Literal lässt sich das nicht hinschreiben. :-)>>> Edit:> Oder hab ich dich jetzt missverstanden und du wolltest genau darauf> hinaus :-)
Im Prinzip ja. Was ich einfach damit sagen wollte:
Es ist kein Wunder, dass dieser Sachverhalt dir und mir nicht geläufig
war, weil man nur selten die Zahl -32768 in dieser Form hinschreibt.
Wenn man diese Konstante tatsächlich benötigt, schreibt man dafür meist
INT_MIN oder vielleicht auch 0x8000. Im ersten Fall tritt das Typproblem
nicht auf, da INT_MIN eben nicht als (-32768), sondern als (-32767 - 1)
definiert ist. Der zweite Fall stellt ebenfalls kein Problem dar, da
0x8000 unsigned int ist und damit die gleiche interne Darstellung wie
(int)(-32768) hat (INT_MIN ist aber sauberer).
Aber selbst wenn man die -32768 ausschreibt, führt das in den meisten
Fällen nicht zum Fehler:
1
voidsub(int);
2
intn,i;
3
longnn;
4
5
n=-32768;// kein Problem, da die Konstante bereits beim
6
// kompilieren in int gecastet wird
7
8
sub(-32768);// dto.
9
10
n=-32768+i;// kein Fehler, aber je nach Compiler wird die
11
// Operation evtl. in long-Arithmetik ausgeführt,
12
// was etwas mehr Zeit kostet
Es ist somit recht unwahrscheinlich, dass man jemals über das Problem
stolpert und deswegen IMHO keine Schande, wenn es nicht kennt :)
Bei
1
printf("%d",-32768);
gibt der GCC übrigens eine Warnung aus, wenn -Wall o.ä. eingestellt ist.
Die Formatüberprüfung kann auch für eigene printf-ähnliche Funktionen
durch Setzen eines entsprechenden Attributs aktiviert werden:
Nikolaus Wirth schrieb:> In C wohl nicht, aber in Pascal:Nikolaus Wirth schrieb:>> p : integer = -32768;
Nach dieser Syntaxdefinition sind auch in Pascal Integer-Literale wie in
C vorzeichenlos:
http://condor.depaul.edu/ichu/csc447/notes/wk2/pascal.html
Dein Beispiel würde auch in C korrekt funktionieren, da in diesem
Kontext der Compiler die -32768 implizit in int casten würde. Das
Problem stellt sich vor allem bei Funktionen mit einer variablen Anzahl
von Argumenten, wo der Compiler den gewünschten Zieltyp i.Allg. nicht
erraten kann. Diese Sorte von Funktionen gibt aber m.W. in Pascal nicht.
Vielen Dank für die Erklärung. Wirklich sehr interessant.
Diesen Trick mit
1
#define INT_MIN (-32767-1)
hab ich gleich in mein Projekt übernommen.
Ein Hinweis noch:
Ich hab in meinem printf einen eigenes Format für uint16_t (Wertebereich
0..65535).
Dies funktioniert mit Konstanten auch nur von 0..32767, da alles ab
32768 als "long singed int" interpretiert wird. Ist nach Eurer Erklärung
ja auch nur logisch.
Bronco schrieb:> Diesen Trick mit> #define INT_MIN (-32767-1)> hab ich gleich in mein Projekt übernommen.
Du musst das aber nicht selbst definieren, INT_MIN ist – zusammen mit
weiteren Integer-Wertebereichen – schon in limits.h definiert.
Die Wertebereiche für int8_t, uint16_t usw. sind in stdint.h zu finden
und heißen INT8_MIN, INT8_MAX, UINT16_MIN, UINT16_MAX usw.
Bronco schrieb:> Dies funktioniert mit Konstanten auch nur von 0..32767, da alles ab> 32768 als "long singed int" interpretiert wird. Ist nach Eurer Erklärung> ja auch nur logisch.
Ja.
Aber
50000U
ist ein unsigned int und passt damit wieder.
ein
meinPrintf("%u", 32768U);
müsste also wieder funktionieren.
Und jetzt hast du auch gelernt, wozu es die Postfixe gibt, die man an
Zahlen anhängt, um dem Compiler mitzuteilen als welchen Datentyp er eine
Zahl auffassen soll :-)
Der Unterschied zwischen
2
und
2L
besteht darin, dass ersteres vom Datentyp int ist, während letzteres vom
Datentyp long ist, bei gleichen Zahlenwert.
Yalu X. schrieb:> Du musst das aber nicht selbst definieren, INT_MIN ist – zusammen mit> weiteren Integer-Wertebereichen – schon in limits.h definiert.
Das wußte ich.
> Die Wertebereiche für int8_t, uint16_t usw. sind in stdint.h zu finden> und heißen INT8_MIN, INT8_MAX, UINT16_MIN, UINT16_MAX usw.
Das wußte ich nicht ;)
Danke!
Bronco schrieb:> Gibt es einen Unterschied zwischen> 50000U> und> ((uint16_t)50000)> ?
50000U ist ein unsigned int, ((uint16_t)50000) ein uint16_t. Für eine
16-Bit-CPU wie dem dsPIC33 ist das normalerweise der gleiche Datentyp,
auf einer 32-Bit-CPU aber nicht.
Yalu X. schrieb:> 50000U ist ein unsigned int, ((uint16_t)50000) ein uint16_t. Für eine> 16-Bit-CPU wie dem dsPIC33 ist das normalerweise der gleiche Datentyp,> auf einer 32-Bit-CPU aber nicht.
Okay.
D.h. aber auch, wenn man Datentypen wie int, long etc. vermeiden will
und lieber sint16_t, sint32_t etc. benutzt, sollte man die Finger von
dem Postfix lassen.
Bronco schrieb:> D.h. aber auch, wenn man Datentypen wie int, long etc. vermeiden will> und lieber sint16_t, sint32_t etc. benutzt, sollte man die Finger von> dem Postfix lassen.
Nö. D.h., dass man dann die Makros aus stdint.h nimmt, um den jeweils
passenden Postfix zu erhalten:
Stefan Ernst schrieb:> Nö. D.h., dass man dann die Makros aus stdint.h nimmt, um den jeweils> passenden Postfix zu erhalten:
Wenn man denn ein stdint.h hat. Soweit ich sehen kann, gibt's in der
C30-Installation (v3.25) diesen Header nicht...
Bronco schrieb:> Wenn man denn ein stdint.h hat. Soweit ich sehen kann, gibt's in der> C30-Installation (v3.25) diesen Header nicht...
Und wo soll dann das uint16_t aus deiner Frage herkommen?
> Gibt es einen Unterschied zwischen> 50000U> und> ((uint16_t)50000)
Stefan Ernst schrieb:> Und wo soll dann das uint16_t aus deiner Frage herkommen?
Das habe ich mich auch gefragt :)
Ich glaube, beim C30 sind diese Typen zwar definiert, aber nicht
C99-konform in stdint.h, sondern sonst irgendwo. Dass beim C30 nicht
alles so ist wie man es erwartet, erkennt man auch an den Namen der
Signed-Integer-Typen:
Bronco schrieb:> sint16_t s16 = -32768;
Stefan Ernst schrieb:> Und wo soll dann das uint16_t aus deiner Frage herkommen?
Hab ich mir selber definiert.
Mir war nämlich bis zu dieser Diskussion nicht bekannt, daß es ein
stdint.h gibt ;)