Forum: Compiler & IDEs VarArgs und Konstanten


von Bronco (Gast)


Lesenswert?

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_t s16 = -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?

von Karl H. (kbuchegg)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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:
1
meinPrintf("%i", (int)-32768);

oder
1
meinPrintf("%i", -32767-1);

oder
1
meinPrintf("%i", -32768u);

oder
1
meinPrintf("%i", 0x8000);

von Oliver (Gast)


Lesenswert?

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

von Josef D. (jogedua)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von Klaus (Gast)


Lesenswert?

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

von der mechatroniker (Gast)


Lesenswert?

> 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.

von Karl H. (kbuchegg)


Lesenswert?

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.
>
>
1
a = -b;
> oder auch
>
1
a = 3 * -b;

Hmm.
Gutes Argument.

von Karl H. (kbuchegg)


Lesenswert?

Ich hab mal im Standard geschmökert

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

und als Folge dessen laufe ich zu Yalu über. Ist eigentlich recht 
eindeutig, war mir aber noch nie so richtig bewusst
1
6.4.4.2
2
An integer constant begins with a digit

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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?

von Michi (Gast)


Lesenswert?

Yalu X. schrieb:
> Wie oft benutzt man schon INT_MIN als Dezimalzahl ausgeschrieben?

z.B. beim Comnpilerbau, um eben genau diese Grenzen abzustecken.

von Hmm (Gast)


Lesenswert?

>z.B. beim Comnpilerbau, um eben genau diese Grenzen abzustecken.

Ach was!

von Karl H. (kbuchegg)


Lesenswert?

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.

von Nikolaus Wirth (Gast)


Lesenswert?

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
program sint;
2
3
VAR
4
  b,
5
  sint: integer;  // hier 16bit (Wertebereich: -32768..+32767)
6
7
CONST
8
  p integer = -32768;
9
10
begin
11
  b := p;
12
13
  sint := p;           // = -32768
14
  sint := -1 * (p+1);  // = +32767
15
end.

Kompiliert ohne Fehler.

Grüße Nikolaus Wirth

von Nikolaus Wirth (Gast)


Lesenswert?

sollte:
1
> CONST
2
>   p : integer = -32768;
heissen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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
void sub(int);
2
int n, i;
3
long nn;
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:
1
int meinPrintf(const char*fmt, ...) __attribute__ ((__format__ (__printf__, 1, 2)));

Im Beispiel des TE würde der GCC dann melden:
1
test.c:6:3: warning: format '%i' expects argument of type 'int', but argument 2 has type 'long int' [-Wformat=]
2
   meinPrintf("%i\n", -32768);
3
   ^

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Habe gerade gesehen, dass es auch in C++ trotz des typsichereren cout
als Ersatz für printf vergleichbare Probleme gibt:
1
#include <iostream>
2
3
int main() {
4
5
  std::cout << -2147483648 << std::endl;
6
  return 0;
7
}

Ausgabe auf einem 32-Bit-PC mit C++98:
1
2147483648

Die Warnung des GCC liefert den Grund für dieses Verhalten:
1
test.cpp:5:3: warning: this decimal constant is unsigned only in ISO C90 [enabled by default]
2
   std::cout << -2147483648 << std::endl;
3
   ^

Kompiliert mit C++11 warnt der Compiler nicht, und es wird das erwartete
Ergebnis geliefert:
1
-2147483648

von Bronco (Gast)


Lesenswert?

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.
1
meinPrintf("%u", 32768);
gibt als "0" aus, aber
1
meinPrintf("%u", (uint16_t)32768);
funktioniert korrekt.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von Bronco (Gast)


Lesenswert?

Gibt es einen Unterschied zwischen
1
50000U
 und
1
((uint16_t)50000)
?

von Bronco (Gast)


Lesenswert?

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!

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Bronco (Gast)


Lesenswert?

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.

von Stefan E. (sternst)


Lesenswert?

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:
1
uint16_t x = UINT16_C(32768);

von Bronco (Gast)


Lesenswert?

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...

von Stefan E. (sternst)


Lesenswert?

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)

von Yalu X. (yalu) (Moderator)


Lesenswert?

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;

von Bronco (Gast)


Lesenswert?

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 ;)

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
Noch kein Account? Hier anmelden.