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
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 ):
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:
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
@ 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.
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.
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.
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?
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.
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_:
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.
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.
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.
>> 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".
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.
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 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.
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;
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
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.
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.
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.
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
typedefunsignedlongintuint64_t;
3
#else
4
__extension__
5
typedefunsignedlonglongintuint64_t;
6
#endif
Da wird einem schnell klar, dass es nicht so klug ist, von einem festen
Typ für uint64_t auszugehen.
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.
> 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.
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.