Forum: PC-Programmierung C: stdint und Formatkennzeichner


von Dennis S. (eltio)


Lesenswert?

Guten Morgen.

Ich habe mir angwöhnt (fast) nur noch die Datentypen aus dem Header 
stdint.h zu benutzen. Allerdings renne ich immer wieder in eine 
Compiler-Warnung bei scanf() wegen des falschen Formatkennzeichners.
1
warning: format '%d' expects argument of type 'int *', but argument 2 has type 'uint64_t *'
2
3
warning: format '%ld' expects argument of type 'long int *', but argument 2 has type 'uint64_t *'

Wie löst man diese Warnung am elegantesten?

Gruß
Dennis

von Moritz (Gast)


Lesenswert?

Schaue einfach mal die jeweilige Definition des fraglichen Typs in 
stdint nach.
Ich sehe z.B. in einem (alten) WinAVR
1
typedef unsigned long long int uint64_t;

Der Format-Specifier dazu ist: "%llu".

Aber ACHTUNG: Dieser (und andere Specifier) ist/sind erst mit C99 zum 
Standard hinzugekommen.

von mh (Gast)


Lesenswert?

siehe inttypes.h:

uint64_t -> PRIu64

bsp:
printf("%"PRIu64"\n", UINT64_MAX);

von Dr. Sommer (Gast)


Lesenswert?

Moritz schrieb:
> Schaue einfach mal die jeweilige Definition des fraglichen Typs in
> stdint nach.

Das ist genau das falsche Vorgehen, denn die Definition kann sich je 
nach Plattform/Compiler-Version ändern. Korrekterweise verwendet man die 
Makros aus der <inttypes.h>  (siehe 
http://en.cppreference.com/w/c/types/integer ):
1
#include <inttypes.h>
2
3
int main () {
4
  uint8_t i = 42;
5
  printf ("Die Zahl ist: %" PRIu8 "\n", i);
6
}

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

C99 definiert in inttypes.h Konstanten für printf/scanf, die statt der 
direkten Formatspezifizerer verwendet werden können, und somit 
"automagisch" passen.

Das ist z.B. PRId64 und wird wie folgt genutzt:
1
printf("Das sind %" PRId64 " Gurken\n", x);

Bei Scanf heißt die Konstante korrespondierend SCNd64 und wird so 
genutzt:
1
sscanf(string, "%" SCNd64, &x);

: Bearbeitet durch User
von Dennis S. (eltio)


Lesenswert?

Moritz schrieb:
> Schaue einfach mal die jeweilige Definition des fraglichen Typs in
> stdint nach.

Neee... genau, das ist ja der nicht portable Weg. Aber danke für deine 
Anmerkung.

Vielen Dank für den Hinweis auf die inttypes.h

Gruß
Dennis

von Moritz (Gast)


Lesenswert?

@ Dr. Sommer

Für genau falsch halte ich das Vorgehen nicht. Denn es funktioniert ja.

Du hast allerdings recht, dass mein Vorgehen nicht portabel ist und man 
bei jeder Portierung wieder nachschauen müsste.

Dein Vorschlag hat also Vorteile und meiner demgegenüber Nachteile. 
"Falsch" ist was anderes. "Falsch" würde zu Fehlern führen und das tut 
es nicht.

von Dr. Sommer (Gast)


Lesenswert?

PS: Die einfachste Variante ist es eine Sprache mit Overloads zu nutzen, 
wie C++:
1
std::cout << i;

Funktioniert unabhängig vom Typ von i immer, natürlich auch wenn man den 
später noch ändert. Leider sind die Stream Libraries nicht so für 
Mikrocontroller optimiert, aber am PC funktioniert es super.

von Dr. Sommer (Gast)


Lesenswert?

Moritz schrieb:
> Du hast allerdings recht, dass mein Vorgehen nicht portabel ist und man
> bei jeder Portierung wieder nachschauen müsste.
Die Verwendung von den uintX_t Typen macht nur Sinn, wenn der Code 
portabel sein soll. Wenn du onehin keine Portabilität haben möchtest, 
kannst du auch einfach "unsigned long long int" nehmen. Erst portabel 
mit "uint64_t" und dann unportabel mit "%llu" sein ist sinnlos.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Nur ist die formatierte Ausgabe bei Verwendung der Streamoperatoren 
ein ziemlicher umständlicher Krampf im Arsch ...

Wie instruierst Du das, die Zahl mit einer Feldbreite von 5 Stellen und 
führenden Nullen auszugeben?

Wie machst Du das gleiche mit hexadezimaler Notation?

von mh (Gast)


Lesenswert?

Dr. Sommer schrieb:
> PS: Die einfachste Variante ist es eine Sprache mit Overloads zu
> nutzen,
> wie C++:
> std::cout << i;
> Funktioniert unabhängig vom Typ von i immer, natürlich auch wenn man den
> später noch ändert. Leider sind die Stream Libraries nicht so für
> Mikrocontroller optimiert, aber am PC funktioniert es super.

Dafür ist wirklich formatierte Ausgabe (Anzahl Ziffern/Anzahl 
Nachkommastellen/führende Nullen/...) mit iostreams in C++ nen Krampf.

von Dr. Sommer (Gast)


Lesenswert?

Rufus Τ. F. schrieb:
> Nur ist die formatierte Ausgabe bei Verwendung der
> Streamoperatoren
> ein ziemlicher umständlicher Krampf im Arsch ...
>
> Wie instruierst Du das, die Zahl mit einer Feldbreite von 5 Stellen und
> führenden Nullen auszugeben?
>
> Wie machst Du das gleiche mit hexadezimaler Notation?
Ja das sieht in der Tat etwas komisch aus, funktioniert dafür immer 
unabhängig vom Typ der Zahl, _auch wenn man ihn nachträglich ändert_:
1
int i = 42;
2
std::cout << std::setw(5) << std::setfill('0') << i << std::endl;
3
std::cout << std::setw(5) << std::setfill('0') << std::hex << i;

von Moritz (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Moritz schrieb:
>> Du hast allerdings recht, dass mein Vorgehen nicht portabel ist und man
>> bei jeder Portierung wieder nachschauen müsste.

> Die Verwendung von den uintX_t Typen macht nur Sinn, wenn der Code
> portabel sein soll.
> Wenn du onehin keine Portabilität haben möchtest,
> kannst du auch einfach "unsigned long long int" nehmen. Erst portabel
> mit "uint64_t" und dann unportabel mit "%llu" sein ist sinnlos.

Das hängt davon ab, was der beabsichtigte Sinn gewesen wäre.
Er muss nicht zwingend Portabilität beinhaltet haben.
Es mag ja auch sein, dass jemand sich lediglich leichter merkt das 
uint64_t einfach unsigned und 64 Bit lang ist und sonst nichts.

Ich gebe Dir recht, dass Dein Vorschlag insgesamt gesehen besser ist 
weil er die Portabilität in höherem Maße berücksichtigt. Aber "falsch" 
oder "sinnlos" ist was anderes. Es werden hier so leicht kategorische 
Urteile gefällt und oft fehlerhaft; darauf reagiere ich hiermit.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Moritz schrieb:
> Es mag ja auch sein, dass jemand sich lediglich leichter merkt das
> uint64_t einfach unsigned und 64 Bit lang ist und sonst nichts.

Aber das kann man sich doch auch ganz einfach mit PRIu64 merken. "PRI" 
wie "printf", "u" wie unsigned und 64 wie 64.

von Tom (Gast)


Lesenswert?

Moritz schrieb:
> Es werden hier so leicht kategorische
> Urteile gefällt

Wenn man zuviele Stunden seines Lebens damit verbracht hat, Dinge zu 
reparieren, die ein Vorgänger nach dem Grundsatz "Funktioniert auf 
meinem PC, muss also richtig sein" programmiert hat, reagiert man etwas 
allergisch auf halbe Lösungen. Besonders wenn -- wie in diesem Fall -- 
der Mehraufwand für den korrekten Weg ungefähr Null gewesen wäre.

von Lug'n'Trug (Gast)


Lesenswert?

>> Es werden hier so leicht kategorische
>> Urteile gefällt
> reagiert man etwas
> allergisch auf halbe Lösungen.

Der Kommentar kommt ja nicht ungefragt: der TO wollte etwas.

Es ist nur Gut[TM] wenn mehrere MÖGLICHKEITEN zur Antwort auftauchen.

Es ist nur Besser[TM] wenn diese AUCH begründet werden.

Der TO kann dann nach seinem Geschmack aussuchen, für was er sich 
entscheidet muss er sich hier ja nicht rechtfertigen.

Nicht zuletzt hilft es auch einem späteren Leser dieses Threads.

Und Ich? Ich bin allergisch gegen knappe Antworten a la "nimm 42 und gut 
iss".

von Yalu X. (yalu) (Moderator)


Lesenswert?

Moritz schrieb:
> Für genau falsch halte ich das Vorgehen nicht. Denn es funktioniert ja.

Neben der mangelnden Portabilität ist die Verwendung von "%llu" für
uint64_t auch unsauber. Das meint auch GCC (auf einem 64-Bit-Linux-PC):

1
warning: format ‘%llu’ expects argument of type ‘long long unsigned int’,
2
         but argument 2 has type ‘uint64_t {aka long unsigned int}’ [-Wformat=]

Die Ausgabe funktioniert trotzdem, da auf diesem Rechner

  long long unsigned int

und

  long unsigned int

zwar verschiedene Datentypen sind, aber dieselbe interne Darstellung (64
Bit) haben.

Mit "%lu" statt "llu" würde die Warnung verschwinden, dafür würde das
Programm auf einem 32-Bit-Windows-PC nicht mehr funktionieren.

Mit "%"PRIu64 bzw. "%"SCNu64 geht man all diesen Problemen elegant aus
dem Weg.

: Bearbeitet durch Moderator
von von Standards, Fan (Gast)


Lesenswert?

Bin auch froh von PRIn64/SCNn64 et al. vernommen zu haben. Habe mir oft 
ad-hoc-DIY-Krücken gebastelt, dabei nicht über die Nase aus gedacht dass 
da was vorbereitetes sein könnte.
Will mir *inttypes.h* genauer ansehen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

von Standards, Fan schrieb:
> Bin auch froh von PRIn64/SCNn64 et al. vernommen zu haben.

Genau genommen war inttypes.h sogar vor stdint.h da, und enthielt
sowohl die Typnamen als auch die printf/scanf-Formate.  Erst später
während der Standardisierung hat man die beiden entkoppelt.

von Programmiersprachentheaterintendant (Gast)


Lesenswert?

wiedermal ein griffiges Beispiel warum es schwierig ist in "C" guten 
Code zu schreiben resp. es einfach ist in soviele Stolperfallen zu 
treten.

C als Sprache an sich ist zwar flexibel aber auch alt und zu viel 
abhängig vom Macher des Compilers.

Die später hinzugekommenen Bibliotheken (hier inttypes.h) versuchen (und 
es gelingt ihnen!) Mängel zu beseitigen.

Bloss: für Einsteigende/Lernende ist es reine GLÜCKSACHE den 
richtigen Pfad/Tutor zu erwischen, den ihn an solchen Klippen 
vorbeiführt ohne einfach von Infoflut a la google-ergebnisse überrollt 
zu werden.
Eben: bloss von compilierendem Code abzukupfern ist nicht die ganze 
Wahrheit, wenn man die Vorlage nicht bewerten kann.

Das Sahnehäubchen: so gut wie alle Schulbeispiele (s. z.B. der klassiker 
F<->C Umrechner in K&R "The C Programming Language", aber auch nicht 
Bruce Eckel, etc.) gehen nicht auf PRIn64 und Kollegen ein. Und bitte: 
seit dem Erstdruck vor über 4 Jahrzehnte ist genügen Zeit verstrichen 
und sind genug sog. überarbeitete Auflagen erschienen wo genau solches 
hätte einfliessen MÜSSEN.


Fazit:
- C lernen = 50% Archäologie, was nicht sein müsste;
- Weil's die Wenigsten tun = zu viel schlechter Code im Umlauf;

von Dennis S. (eltio)


Lesenswert?

Yalu X. schrieb:
> Neben der mangelnden Portabilität ist die Verwendung von "%llu" für
> uint64_t auch unsauber. Das meint auch GCC (auf einem 64-Bit-Linux-PC):
>
> warning: format ‘%llu’ expects argument of type ‘long long unsigned
> int’,
>          but argument 2 has type ‘uint64_t {aka long unsigned int}’
> [-Wformat=]
Genau das war ja auch mein Ausgangsproblem.

Von mir noch abschließend die Anmerkung: Immer wieder witzig wenn auf 
die Frage "Wie mache ich das in C?" Die Antwort kommt "Nimm C++". :-D

Gruß
Dennis

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Programmiersprachentheaterintendant schrieb:
> sind genug sog. überarbeitete Auflagen erschienen

Meines Wissens gibt's vom K&R bislang keine überarbeitete Auflage,
die C99 (oder neuer) beinhaltet.  Da DMR inzwischen gestorben ist,
wird zumindest er wohl auch nicht mehr an einer arbeiten können …

Aber ganz ehrlich: so schwer ist an diese Infos nun auch nicht
ranzukommen.  Es war ja hier nicht nur einer, der diesen Tipp gegeben
hat, und all die, die es wissen, müssen's auch irgendwoher erfahren
haben.  Da bei C der Standard de facto öffentlich ist (auch wenn die
elektronisch frei verfügbaren Dokumente aus juristischen Gründen
“draft” heißen), ist es auch nicht so sehr schwer, sich diesen mal
anzusehen.

von Moritz (Gast)


Lesenswert?

Yalu X. schrieb:
> Moritz schrieb:
>> Für genau falsch halte ich das Vorgehen nicht. Denn es funktioniert ja.
>
> Neben der mangelnden Portabilität ist die Verwendung von "%llu" für
> uint64_t auch unsauber. Das meint auch GCC (auf einem 64-Bit-Linux-PC):
>

Na toll. Ich präsentiere die Vorgehensweise anhand eines alten WinAVR 
(wie ich auch schrieb) und das geht nicht auf mit nem GCC auf einem 
64-Bit-Linux-PC.

WAS für eine Überraschung. :-)

Bei Dir allerdings weniger Ego sondern ein Versehen, vermute ich.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Moritz schrieb:
> WAS für eine Überraschung. :-)

Die dir aber eben erspart bliebe, wenn du die Formatbezeichner aus
<stdint.h> benutzt.

von Moritz (Gast)


Lesenswert?

Jörg W. schrieb:
> Moritz schrieb:
>> WAS für eine Überraschung. :-)
>
> Die dir aber eben erspart bliebe, wenn du die Formatbezeichner aus
> <stdint.h> benutzt.

Wieso mir?

Yalu hat das doch versucht und präsentierst das Ergebnis als etwas 
Bemerkenswertes.

Ich hatte nur Grund anzunehmen, dass das mit der von mir verwendeten 
Version von WinAVR funktioniert.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Moritz schrieb:
> Na toll. Ich präsentiere die Vorgehensweise anhand eines alten WinAVR
> (wie ich auch schrieb) und das geht nicht auf mit nem GCC auf einem
> 64-Bit-Linux-PC.
>
> WAS für eine Überraschung. :-)
>
> Bei Dir allerdings weniger Ego sondern ein Versehen, vermute ich.

Ja, das habe ich allerdings übersehen, zumal der Thread im Forum
"PC-Programmierung" läuft.

Moritz schrieb:
> Schaue einfach mal die jeweilige Definition des fraglichen Typs in
> stdint nach.
> Ich sehe z.B. in einem (alten) WinAVR
> typedef unsigned long long int uint64_t;

Aber dieses Nachschlagen kann man sich bei der Verwendung der PRI*- und
SCN*-Makros sparen, wenn einmal verstanden hat, nach welchem Schema
diese aufgebaut sind.

Auf dem PC sieht der entsprechende Ausschnitt von stdint.h übrigens so
aus:

1
#if __WORDSIZE == 64
2
typedef unsigned long int  uint64_t;
3
#else
4
__extension__
5
typedef unsigned long long int  uint64_t;
6
#endif

Da wird einem schnell klar, dass es nicht so klug ist, von einem festen
Typ für uint64_t auszugehen.

von Dirk B. (dirkb2)


Lesenswert?

Yalu X. schrieb:
> Auf dem PC sieht der entsprechende Ausschnitt von stdint.h übrigens so
> aus:
>
> #if __WORDSIZE == 64
> typedef unsigned long int  uint64_t;
> #else
> __extension__
> typedef unsigned long long int  uint64_t;
> #endif
>
Das gilt dann aber nur für Linux.

von Programmiersprachentheaterintendant (Gast)


Lesenswert?

> Meines Wissens gibt's vom K&R bislang keine überarbeitete Auflage,
> die C99 (oder neuer) beinhaltet.  Da DMR inzwischen gestorben ist,
> wird zumindest er wohl auch nicht mehr an einer arbeiten können …

Gebe ich Dir.

> Aber ganz ehrlich: so schwer ist an diese Infos nun auch nicht
> ranzukommen.

Nein, aber sie aus dem restlichen Müll rauszufiltern.


> hat, und all die, die es wissen, müssen's auch irgendwoher erfahren
> haben.

Meist eben dur eigenes, bitteres, Aufdienasefallen. Leider viel zu 
selten ab Tag 1 seit Einsatzt von *printf() / *scanf().

Beweis: so viele Fragen nach Formatstrings in Foren...

>  Da bei C der Standard de facto öffentlich ist (auch wenn die
> elektronisch frei verfügbaren Dokumente aus juristischen Gründen
> “draft” heißen), ist es auch nicht so sehr schwer, sich diesen mal
> anzusehen.

Verfügbarkeit != Lesbarkeit (insbes.f. Einsteiger)

Zudem ist diese Lektüre so umfangreich, da kracht jeder Nachttisch 
zusammen wenn man's dahinlegt zwecks Schlaflosigkeitsbekämpfung.

von Rolf M. (rmagnus)


Lesenswert?

Moritz schrieb:
> @ Dr. Sommer
>
> Für genau falsch halte ich das Vorgehen nicht. Denn es funktioniert ja.

Es funktioniert auf dem System, an dem man gerade arbeitet. Die ganze 
Idee hinter den Typen aus stdint.h ist aber gerade, davon unabhängig zu 
sein. Deshalb widerspricht es komplett dem Konzept.

Moritz schrieb:
>> Die Verwendung von den uintX_t Typen macht nur Sinn, wenn der Code
>> portabel sein soll.
>> Wenn du onehin keine Portabilität haben möchtest,
>> kannst du auch einfach "unsigned long long int" nehmen. Erst portabel
>> mit "uint64_t" und dann unportabel mit "%llu" sein ist sinnlos.
>
> Das hängt davon ab, was der beabsichtigte Sinn gewesen wäre.
> Er muss nicht zwingend Portabilität beinhaltet haben.
> Es mag ja auch sein, dass jemand sich lediglich leichter merkt das
> uint64_t einfach unsigned und 64 Bit lang ist und sonst nichts.

Wenn er das dann aber mit %llu in scanf verwendet, muss er zusätzlich 
dazu auch wissen, dass auf seiner Plattform unsigned long long genauso 
groß ist wie uint64_t, denn nur dann funktioniert es. Spätestens dann 
hätte er sich aber das uint64_t auch gleich sparen können. Es gibt 
meiner Ansicht nach keinen sinnvollen Grund, die Typen aus stdint.h zu 
verwenden, aber nicht die dazugehörigen Format-Makros.

> Ich gebe Dir recht, dass Dein Vorschlag insgesamt gesehen besser ist
> weil er die Portabilität in höherem Maße berücksichtigt. Aber "falsch"
> oder "sinnlos" ist was anderes.

Nein, ist es nicht.

Dirk B. schrieb:
> Das gilt dann aber nur für Linux.

Nö. Das gilt auch für BSD, OS X, Solaris und andere. Außer Windows 
scheint es allgemein kaum ein 64-Bit-Betriebssystem zu geben, bei dem 
long nicht 64 Bit breit is.

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.