VarArgs und Konstanten

Gast #3173887
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?
Moderator #3174003
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);
#3174062
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.
Gast #3174083
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
Gast #3174091
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.
#3174100
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.
#3174123
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.
#3174175
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.
Gast #3174202
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
Moderator #3174529
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
   ^
Moderator #3174541
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.
Moderator #3174559
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
Gast #3174699
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.
Moderator #3174714
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.
#3174718
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.
Gast #3174878
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!
Gast #3175013
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.
Moderator #3177046
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;

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren