[C]printf(3) mit %hd und uint16_t

Herakles

Profifragensteller
Moin!

Folgende simpel-Programm sei gegeben:

Code:
#include <stdio.h>
#include <stdint.h>

int main() {
     uint16_t w=0x9999;
     printf("%d\n",w);
     return 0;
}

Folgendes ist die Ausgabe:

Code:
39321

Lese ich nun aber die man-page von printf(3), finde ich dort folgendes:

The length modifier
Here, "integer conversion" stands for d, i, o, u, x, or X conversion.

(...)

h A following integer conversion corresponds to a short int or unsigned short int
argument, or a following n conversion corresponds to a pointer to a short int
argument.

Demzufolge sollte mein Programm eigentlich wie folgt aussehen:


Code:
#include <stdio.h>
#include <stdint.h>

int main() {
     uint16_t w=0x9999;
     printf("%hd\n",w);
     return 0;
}

Genau hinschauen: Da steht ein "%hd" anstelle eines "%d" im printf-Befehl. Witzigerweise ist die Ausgabe nun so:

Code:
-26215

Warum ist das so?

Viele Grüße
Herakles
 
Warum ist das so?
Weil du %i und nicht %u bzw. %hu nimmst.

Der Code
Code:
#include <stdio.h>
#include <stdint.h>

int main() {
  uint16_t w=0x9999;
  printf("%hd\n",w);
  printf("%d\n",w);
  printf("%hu\n",w);
  printf("%u\n",w);
  return 0;
}
liefert:
Code:
-26215
39321
39321
39321
Bei
  • %hd interpretiert der Compiler das Bitmuster als vorzeichenbehafteten 16-Bit-Integer
  • %d interpretiert der Compiler das Bitmuster als vorzeichenbehafteten Integer (moment, könnte man da nicht falsch auf den Speicher zugreifen????)
  • %hu interpretiert der Compiler das Bitmuster als vorzeichenlosen 16-Bit-Integer
  • %u interpretiert der Compiler das Bitmuster als vorzeichenbehafteten Integer (moment, könnte man da nicht falsch auf den Speicher zugreifen????)
Bei %hd wird es so sein, dass das von Dir ausgewählte Bitmuster das Vorzeichenbit gesetzt hat, der restliche Wert ergibt sich als zweierkomplement (siehe http://de.wikipedia.org/wiki/Arithmetischer_Überlauf ).

HTH
chaos
 
Noch was:
Ist die Nutzung eines nichtpassenden Formatstring nicht rein formal undefiniert (d.h. der Compiler darf rein formal gesehen ab diesem Zeitpunkt einfach irgendeinen Objektcode generieren)?

Interessant ist auch, was passiert, wenn man int16_t durch short ersetzt.
 
C erweitert Werte zum Rechnen mindestens auf int. Deswegen kannst du Integer <= int als int betrachten und mit %i ausgeben.
 
Ok, das ist spitzfindig, stimmt aber natürlich, da kann die Intgral promotion angewendet werden. Aus dem draft zu C90 (http://flash-gordon.me.uk/ansi.c.txt )
Code:
  A char, a short int, or an int bit-field, or their signed or
unsigned varieties, or an object that has enumeration type, may be
used in an expression wherever an int or unsigned int may be used.  If
an int can represent all values of the original type, the value is
converted to an int; otherwise it is converted to an unsigned int.
These are called the integral promotions.
 
uint16_t ist ein typedef, der in C99 neu eingeführt wurde (stdint.h). Analog wurden in inttypes.h Makros definiert, mit denen man diese Datentypen ausgeben kann. Schön sind sie zwar nicht, aber portabel.
Bestehen tun sie aus der Zeichenfolge PRI, dann kommt der entsprechende Format-specifier (d, u, x,…) und dann die Breite.
Das Resultat wäre also
Code:
#include <inttypes.h>
#include <stdio.h>
#include <stdint.h>

int main() {
     uint16_t w=0x9999;
     printf("%"PRIu16"\n",w);
     return 0;
}

Das Problem mit den klassischen Formatstrings ist, dass eigentlich keinerlei Typüberprüfung stattfinden kann, da es sich um variadische Funktionen handelt. Aber sowohl gcc als auch clang haben einiges an Logik, um die Standardformate zu überprüfen, Warnungen hochschrauben hilft.

PS: Portabel ist relativ, da Microsoft immer noch kein C99 kann, obwohl C++11 es als subset definiert hat. Portabel gilt daher nur in Bezug auf andere Prozessoren mit anderen Wortbreiten, nicht auf andere Betriebssysteme…
 
Aber doch nur in Bezug auf printf. Man kann ja schließlich auch eigene Funktionen mit eigenen Formatstrings bauen.

Edit: Typo.
 
Eigentlich ja. Da wird man aber wohl oder übel mit va_arg arbeiten müssen und wenn man va_arg beim Typ nicht die Wahrheit sagt, ist das Ergebnis halt undefiniert (§7.15.1.1/2). Das ist vermutlich auch der Grund für o.g. Verhalten bei *printf.
 
Danke Euch allen für die (übliche) fundierte Diskussion. Meien Lösung lautet demzufolge:

Code:
#include <stdio.h>
#include <stdint.h>

int main() {
uint16_t w=0x9999;
printf("%hu\n",w);
return 0;
}

Zack - wieder wat gelernt!

Hervorragend!
Herakles
 
Hab es eben va_list benutzt, clang mit -Weverything ist da sehr genau, es wird unbedingt ein __attribute__((format(printf, n, m))) verlangt, damit der Formatstring überprüft wreden kann.

@Herakles: Möchtest du nur auf POSIX-konformen Systemen laufen? Andernfalls ist das nach C-Standard eben nicht korrekt, da sizeof(short) < 2 legal ist.
So häßlich die PRI-Makros sind, in Verbindung mit C99-Integern sind sie die einzige portable und korrekte (IMHO) Lösung.
 
Zurück
Oben