Wie kann man eine size_t-Variable portabel mit der printf-Familie drucken?

404

Ich habe eine Variable vom Typ size_tund möchte sie mit drucken printf(). Welchen Formatbezeichner verwende ich, um es portabel zu drucken?

In 32-Bit-Maschine %uscheint richtig. Ich habe mit kompiliert g++ -g -W -Wall -Werror -ansi -pedanticund es gab keine Warnung. Wenn ich diesen Code jedoch auf einem 64-Bit-Computer kompiliere, wird eine Warnung ausgegeben.

size_t x = <something>;
printf("size = %u\n", x);

warning: format '%u' expects type 'unsigned int', 
    but argument 2 has type 'long unsigned int'

Die Warnung verschwindet wie erwartet, wenn ich das in ändere %lu.

Die Frage ist, wie kann ich den Code schreiben, damit er auf 32- und 64-Bit-Computern warnungsfrei kompiliert wird?

Bearbeiten: Als Problemumgehung könnte eine Antwort darin bestehen, die Variable in eine Ganzzahl umzuwandeln, die beispielsweise groß genug ist unsigned long, und mit zu drucken %lu. Das würde in beiden Fällen funktionieren. Ich suche, ob es eine andere Idee gibt.

Arun
quelle
4
Casting to unsigned longist die beste Option, wenn Ihre libc-Implementierung den zModifikator nicht unterstützt . Der C99-Standard empfiehlt size_t, keinen ganzzahligen Konvertierungsrang größer als zu haben long, damit Sie einigermaßen sicher sind
Christoph
1
Auf der Windows-Plattform kann size_t größer als lang sein. Aus Kompatibilitätsgründen ist long immer 32-Bit, size_t kann jedoch 64-Bit sein. Wenn Sie also auf vorzeichenloses Long werfen, kann die Hälfte der Bits verloren gehen. Entschuldigung :-)
Bruce Dawson

Antworten:

482

Verwenden Sie den zModifikator:

size_t x = ...;
ssize_t y = ...;
printf("%zu\n", x);  // prints as unsigned decimal
printf("%zx\n", x);  // prints as hex
printf("%zd\n", y);  // prints as signed decimal
Adam Rosenfield
quelle
7
+1. Ist dies eine C99-Ergänzung oder gilt dies auch für C ++ (ich habe C90 nicht zur Hand)?
Avakar
6
Es ist eine C99-Ergänzung und nicht in der Liste der printf()Längenmodifikatoren des C ++ 0x-Entwurfs vom 09.11.2009 (Tabelle 84 auf Seite 672) enthalten
Christoph
3
@Christoph: Auch nicht im neuesten Entwurf, n3035.
GManNickG
11
@avakar @Adam Rosenfield @Christoph @GMan: In n3035 §1.2 Normative Verweise wird jedoch nur auf den C99-Standard verwiesen, und §17.6.1.2 / 3 desselben Status "Die Einrichtungen der C-Standardbibliothek werden bereitgestellt." Ich würde dies so interpretieren, dass, sofern nicht anders angegeben, alles in der C99-Standardbibliothek Teil der C ++ 0x-Standardbibliothek ist, einschließlich der zusätzlichen Formatspezifizierer in C99.
James McNellis
9
@ArunSaha: Es ist eine Funktion von nur C99, nicht von C ++. Wenn Sie möchten, dass es kompiliert wird -pedantic, benötigen Sie entweder einen Compiler, der den C ++ 1x-Entwurf unterstützt (höchst unwahrscheinlich), oder Sie müssen Ihren Code in eine Datei verschieben, die als C99 kompiliert wurde. Andernfalls ist Ihre einzige Option Ihre Variablen zu werfen unsigned long longund verwenden %llumaximal tragbar zu sein.
Adam Rosenfield
88

Sieht so aus, als ob es je nach verwendetem Compiler unterschiedlich ist (blech):

... und natürlich, wenn Sie C ++ verwenden, können Sie coutstattdessen verwenden, wie von AraK vorgeschlagen .

TJ Crowder
quelle
3
zwird auch von newlib (dh cygwin) unterstützt
Christoph
7
%zdist falsch für size_t; Es ist korrekt für den entsprechenden signierten Typ size_t, aber size_tselbst ist ein nicht signierter Typ.
Keith Thompson
1
@KeithThompson: Ich habe es auch erwähnt %zu(und %zxfalls sie Hex wollen). Richtig, das %zuhätte wahrscheinlich an erster Stelle in der Liste stehen sollen. Fest.
TJ Crowder
10
@TJCrowder: Ich denke nicht, dass %zdes überhaupt in der Liste sein sollte. Ich kann mir keinen Grund vorstellen, einen Wert zu verwenden, %zdanstatt ihn %zuzu drucken size_t. Es ist nicht einmal gültig (hat undefiniertes Verhalten), wenn der Wert überschreitet SIZE_MAX / 2. (Der Vollständigkeit %zo
Keith Thompson
2
@FUZxxl: POSIX erfordert nicht, dass ssize_tder signierte Typ dem entspricht size_t, daher kann keine Übereinstimmung garantiert werden "%zd". (Es ist wahrscheinlich auf den meisten Implementierungen.) Pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
Keith Thompson
59

Verwenden Sie für C89 %luden Wert und wandeln Sie ihn in Folgendes um unsigned long:

size_t foo;
...
printf("foo = %lu\n", (unsigned long) foo);

Verwenden Sie für C99 und höher %zu:

size_t foo;
...
printf("foo = %zu\n", foo);
John Bode
quelle
7
In Anbetracht von 2013 schlagen Sie "Für C99 und höher" und "Für vor C99:" vor. Beste Antwort.
chux
8
Mach das nicht. Es schlägt unter 64-Bit-Windows fehl, wobei size_t 64 Bit und long 32 Bit beträgt.
Yttrill
1
@Yttrill: Was ist dann die Antwort für 64-Bit-Fenster?
John Bode
1
@ JohnBode Vielleicht unsigned long long?
James Ko
2
Oder: Sie können in a umwandeln uint64_tund dann das PRIu64Makro aus inttypes.h verwenden, das den Formatbezeichner enthält.
James Ko
9

Erweiterung der Antwort von Adam Rosenfield für Windows.

Ich habe diesen Code sowohl in VS2013 Update 4 als auch in VS2015 getestet:

// test.c

#include <stdio.h>
#include <BaseTsd.h> // see the note below

int main()
{
    size_t x = 1;
    SSIZE_T y = 2;
    printf("%zu\n", x);  // prints as unsigned decimal
    printf("%zx\n", x);  // prints as hex
    printf("%zd\n", y);  // prints as signed decimal
    return 0;
}

VS2015 generierte Binärausgänge:

1
1
2

während der von VS2013 erzeugte sagt:

zu
zx
zd

Hinweis: ssize_tist eine POSIX-Erweiterung und SSIZE_Tähnelt den Windows-Datentypen . Daher habe ich hinzugefügt<BaseTsd.h> Referenz .

Mit Ausnahme der folgenden C99 / C11-Header sind alle C99-Header in der VS2015-Vorschau verfügbar:

C11 - <stdalign.h>
C11 - <stdatomic.h>
C11 - <stdnoreturn.h>
C99 - <tgmath.h>
C11 - <threads.h>

Auch C11 <uchar.h> jetzt in der neuesten Vorschau enthalten.

Weitere Informationen zur Standardkonformität finden Sie in dieser alten und der neuen Liste.

vulkanischer Rabe
quelle
VS2013 Update 5 liefert die gleichen Ergebnisse wie Update 4.
Nathan Kidd
6

Für diejenigen, die darüber sprechen, dies in C ++ zu tun, das die C99-Erweiterungen nicht unbedingt unterstützt, empfehle ich das Boost :: -Format von Herzen. Dies macht die Frage size_t type size strittig:

std::cout << boost::format("Sizeof(Var) is %d\n") % sizeof(Var);

Da Sie keine Größenangaben im Boost :: -Format benötigen, können Sie sich nur Gedanken darüber machen, wie Sie den Wert anzeigen möchten.

swestrup
quelle
4
Wahrscheinlich wollen %udann.
GManNickG
5
std::size_t s = 1024;
std::cout << s; // or any other kind of stream like stringstream!
AraK
quelle
8
Ja, aber der Fragesteller fragt speziell nach einem printfSpezifizierer. Ich würde vermuten, dass sie einige andere nicht angegebene Einschränkungen haben, die die Verwendung std::couteines Problems erschweren .
Donal Fellows
1
@Donal Ich frage mich, was für ein Problem C ++ - Streams in einem C ++ - Projekt verursachen könnten!
AraK
9
@AraK. Sie sind sehr langsam? Sie fügen aus nicht viel Grund eine Menge Bytes hinzu. ArunSaha möchte es nur für sein persönliches Wissen wissen? Persönliche Präferenz (ich bevorzuge stdio, um mich selbst zu streamen). Es gibt viele Gründe.
KitsuneYMG
1
@TKCrowder: Nun, die ursprüngliche Anfrage besagte, dass eine C-Lösung gewünscht wurde (durch Tagging), und es gibt gute Gründe, Streams in C ++ nicht zu verwenden, z. B. wenn der Deskriptor des Ausgabeformats aus einem Nachrichtenkatalog abgerufen wird. (Sie könnten einen Parser für Nachrichten schreiben und Streams verwenden, wenn Sie möchten, aber das ist eine Menge Arbeit, wenn Sie nur vorhandenen Code nutzen können.)
Donal Fellows
1
@Donal: Die Tags waren C und C ++. Ich befürworte in keiner Weise das E / A-Stream-Material von C ++ (ich bin kein Fan davon) und weise nur darauf hin, dass die Frage ursprünglich nicht * "... Spezifikation für einen printfSpezifizierer fragen ".
TJ Crowder
5
printf("size = %zu\n", sizeof(thing) );
nategoose
quelle
2

Wie AraK sagte, funktioniert die C ++ - Streams-Schnittstelle immer portabel.

std :: size_t s = 1024; std :: cout << s; // oder irgendeine andere Art von Stream wie Stringstream!

Wenn Sie C stdio möchten, gibt es für bestimmte Fälle von "portabel" keine tragbare Antwort darauf. Und es wird hässlich, denn wie Sie gesehen haben, kann die Auswahl der falschen Formatflags eine Compilerwarnung oder eine falsche Ausgabe ergeben.

C99 hat versucht, dieses Problem mit inttypes.h-Formaten wie "%" PRIdMAX "\ n" zu lösen. Aber genau wie bei "% zu" unterstützt nicht jeder c99 (wie MSVS vor 2013). Es gibt "msinttypes.h" -Dateien, die herumschwirren, um damit umzugehen.

Wenn Sie in einen anderen Typ umwandeln, erhalten Sie abhängig von den Flags möglicherweise eine Compiler-Warnung zum Abschneiden oder Ändern des Vorzeichens. Wenn Sie diese Route wählen, wählen Sie einen größeren relevanten Typ mit fester Größe. Eines von unsigned long long und "% llu" oder unsigned long "% lu" sollte funktionieren, aber llu kann in einer 32-Bit-Welt, die übermäßig groß ist, auch die Dinge verlangsamen. (Bearbeiten - Mein Mac gibt eine 64-Bit-Warnung für% llu aus, die nicht mit size_t übereinstimmt, obwohl% lu,% llu und size_t alle dieselbe Größe haben. Und% lu und% llu haben auf meinem MSVS2012 nicht dieselbe Größe Möglicherweise müssen Sie ein passendes Format verwenden.)

In diesem Fall können Sie Typen mit fester Größe verwenden, z. B. int64_t. Aber warte! Jetzt sind wir wieder bei c99 / c ++ 11 und älteres MSVS schlägt erneut fehl. Außerdem hast du auch Casts (zB map.size () ist kein Typ mit fester Größe)!

Sie können einen Header oder eine Bibliothek eines Drittanbieters verwenden, z. B. Boost. Wenn Sie noch keinen verwenden, möchten Sie Ihr Projekt möglicherweise nicht auf diese Weise aufblasen. Wenn Sie nur für dieses Problem einen hinzufügen möchten, verwenden Sie C ++ - Streams oder die bedingte Kompilierung.

Sie sind also auf C ++ - Streams, bedingte Kompilierung, Frameworks von Drittanbietern oder etwas Portables angewiesen, das zufällig für Sie funktioniert.

Rick Berge
quelle
-1

Warnt es Sie, wenn Sie eine 32-Bit-Ganzzahl ohne Vorzeichen an ein% lu-Format übergeben? Es sollte in Ordnung sein, da die Konvertierung genau definiert ist und keine Informationen verliert.

Ich habe gehört, dass einige Plattformen Makros definieren <inttypes.h>, die Sie in das Formatzeichenfolgenliteral einfügen können, aber ich sehe diesen Header in meinem Windows C ++ - Compiler nicht, was bedeutet, dass er möglicherweise nicht plattformübergreifend ist.

Kylotan
quelle
1
Die meisten Compiler werden Sie nicht warnen, wenn Sie etwas von der falschen Größe an printf übergeben. GCC ist eine Ausnahme. inttypes.h wurde in C99 definiert, sodass jeder C99-Compiler, der C99-kompatibel ist, über diese verfügt, die inzwischen alle sein sollten. Möglicherweise müssen Sie C99 jedoch mit einem Compiler-Flag aktivieren. In jedem Fall definiert intttypes.h kein bestimmtes Format für size_t oder ptrdiff_t, da entschieden wurde, dass sie wichtig genug sind, um ihre eigenen Größenangaben von 'z' bzw. 't' zu erhalten.
Swestrup
Wenn Sie verwenden %lu, sollten Sie den size_tWert in umwandeln unsigned long. Es gibt keine implizite Konvertierung (außer Werbeaktionen) für Argumente zu printf.
Keith Thompson
-2

C99 definiert dafür "% zd" usw. (Dank an die Kommentatoren) In C ++ gibt es dafür keinen tragbaren Formatbezeichner - Sie könnten%p dieses Wort verwenden , das in diesen beiden Szenarien nicht funktioniert, aber auch keine tragbare Wahl ist und den Wert in hexadezimaler Form angibt .

Alternativ können Sie Streaming (z. B. Stringstream) oder einen sicheren Printf-Ersatz wie das Boost-Format verwenden . Ich verstehe, dass dieser Rat nur von begrenztem Nutzen ist (und C ++ erfordert). (Bei der Implementierung der Unicode-Unterstützung haben wir einen ähnlichen Ansatz verwendet, der unseren Anforderungen entspricht.)

Das grundlegende Problem für C besteht darin, dass printf, das eine Ellipse verwendet, von Natur aus unsicher ist. Es muss die Größe des zusätzlichen Arguments aus den bekannten Argumenten bestimmen, sodass es nicht behoben werden kann, um "was auch immer Sie haben" zu unterstützen. Wenn Ihr Compiler also keine proprietären Erweiterungen implementiert, haben Sie kein Glück.

Peterchen
quelle
2
Die zGröße modidfier ist Standard C, aber einige libc-Implementierungen stecken 1990 aus verschiedenen Gründen fest (z. B. Microsoft hat C grundsätzlich zugunsten von C ++ und - in jüngerer Zeit - C # aufgegeben)
Christoph
3
C99 definierte den Größenbezeichner 'z' als die Größe eines size_t-Werts und 't' als die Größe eines ptrdiff_t-Werts.
Swestrup
2
%zdist falsch, es ist nicht signiert, so sollte es sein %zu.
David Conrad
-3

Auf einigen Plattformen und für einige Typen stehen bestimmte Druckkonvertierungsspezifizierer zur Verfügung, aber manchmal muss auf das Casting auf größere Typen zurückgegriffen werden.

Ich habe dieses knifflige Problem hier mit Beispielcode dokumentiert: http://www.pixelbeat.org/programming/gcc/int_types/ und es regelmäßig mit Informationen zu neuen Plattformen und Typen aktualisiert.

Pixelbeat
quelle
1
Beachten Sie, dass von Antworten nur mit Links abgeraten wird. SO-Antworten sollten der Endpunkt einer Suche nach einer Lösung sein (im Gegensatz zu einem weiteren Zwischenstopp von Referenzen, die im Laufe der Zeit veralten). Bitte fügen Sie hier eine eigenständige Zusammenfassung hinzu, wobei Sie den Link als Referenz behalten.
Kleopatra
-5

Wenn Sie den Wert von size_t als Zeichenfolge drucken möchten, gehen Sie folgendermaßen vor:

char text[] = "Lets go fishing in stead of sitting on our but !!";
size_t line = 2337200120702199116;

/* on windows I64x or I64d others %lld or %llx if it works %zd or %zx */
printf("number: %I64d\n",*(size_t*)&text);
printf("text: %s\n",*(char(*)[])&line);

Ergebnis ist:

Nummer: 2337200120702199116

Text: Lass uns fischen gehen anstatt auf unserem aber zu sitzen !!

Bearbeiten: Erneutes Lesen der Frage aufgrund der Abstimmungen Ich habe festgestellt, dass sein Problem nicht% llu oder% I64d ist, sondern der Typ size_t auf verschiedenen Computern. Siehe diese Frage https://stackoverflow.com/a/918909/1755797
http: // www. cplusplus.com/reference/cstdio/printf/

size_t ist auf einem 32-Bit-Computer ein Int ohne Vorzeichen und auf einem 64-Bit-Computer ein Long Long Int ohne Vorzeichen
,% ll erwartet jedoch immer ein Long Long Int ohne Vorzeichen.

size_t variiert in der Länge unter verschiedenen Betriebssystemen, während% llu gleich ist

Andre
quelle
4
Was für ein Unsinn ist das?!
Antti Haapala
Die ersten 8 Bytes des char-Arrays durch den size_t-Zeiger in ein vorzeichenloses langes langes 64-Bit zu verwandeln und sie als Zahl mit dem printf% I64d zu drucken, ist nicht wirklich spektakulär, ich weiß, natürlich habe ich keinen Code verwendet, um einen Typüberlauf zu verhindern, aber das ist nicht im Rahmen der Frage.
Andre