536 Stimmen

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

Ich habe eine Variable vom Typ size_t und ich möchte es drucken mit printf() . Welchen Formatspezifikator muss ich verwenden, um es portabel zu drucken?

Auf einem 32-Bit-Rechner, %u scheint richtig zu sein. Ich habe kompiliert mit g++ -g -W -Wall -Werror -ansi -pedantic und es gab keine Warnung. Aber wenn ich den Code auf einem 64-Bit-Rechner 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 %lu .

Die Frage ist, wie kann ich den Code so schreiben, dass er sowohl auf 32- als auch auf 64-Bit-Maschinen warnfrei kompiliert werden kann?

Edit: Als Workaround könnte eine Lösung darin bestehen, die Variable in eine Ganzzahl zu "casten", die groß genug ist, sagen wir unsigned long und drucken mit %lu . Das würde in beiden Fällen funktionieren. Ich bin auf der Suche, ob es eine andere Idee gibt.

627voto

Adam Rosenfield Punkte 373807

Verwenden Sie die z Modifikator:

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

105voto

T.J. Crowder Punkte 948310

Sieht so aus, als ob es davon abhängt, welchen Compiler Sie verwenden (blech):

...und natürlich, wenn Sie C++ verwenden, können Sie auch cout stattdessen als vorgeschlagen von AraK .

74voto

John Bode Punkte 112486

Für C89, verwenden Sie %lu und übergeben den Wert an unsigned long :

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

Für C99 und höher, verwenden Sie %zu :

size_t foo;
...
printf("foo = %zu\n", foo);

12voto

Keith Thompson Punkte 240701

In jeder halbwegs modernen C-Implementierung, "%zu" ist der richtige Weg, um einen Wert des Typs size_t :

printf("sizeof (int) = %zu\n", sizeof (int));

El "%zu" wurde in der ISO C-Norm von 1999 hinzugefügt (und von der ISO C++-Norm von 2011 übernommen). Wenn Sie sich keine Gedanken über ältere Implementierungen machen müssen, können Sie jetzt aufhören zu lesen.

Wenn Ihr Code auf Implementierungen vor C99 portabel sein muss, können Sie den Wert in unsigned long und verwenden "%lu" :

printf("sizeof (int) = %lu\n", (unsigned long)sizeof (int));

Das ist nicht auf C99 oder später übertragbar, weil C99 die long long y unsigned long long und damit die Möglichkeit, dass size_t ist breiter als unsigned long .

Widerstehen Sie der Versuchung, die "%lu" o "%llu" ohne die Besetzung. Der Typ, der zur Implementierung von size_t ist implementierungsabhängig, und wenn die Typen nicht übereinstimmen, ist das Verhalten undefiniert. Etwas wie printf("%lu\n", sizeof (int)); mag "funktionieren", ist aber keineswegs übertragbar.

Im Prinzip gilt Folgendes sollte alle möglichen Fälle abdecken:

#if __STDC_VERSION__ < 199901L
    printf("sizeof (int) = %lu\n", (unsigned long)sizeof (int));
#else
    printf("sizeof (int) = %zu\n", sizeof (int));
#endif

In der Praxis funktioniert dies jedoch nicht immer korrekt. __STD_VERSION__ >= 199901L sollte garantieren, dass "%zu" wird unterstützt, aber nicht alle Implementierungen sind notwendigerweise korrekt, zumal __STD_VERSION__ wird vom Compiler gesetzt und "%zu" wird von der Laufzeitbibliothek implementiert. Zum Beispiel kann eine Implementierung mit teilweise Die C99-Unterstützung könnte Folgendes implementieren long long und machen size_t ein Typendefinition für unsigned long long , aber keine Unterstützung "%zu" . (Eine solche Implementierung würde wahrscheinlich nicht definieren __STDC_VERSION__ .)

Es wurde bereits darauf hingewiesen, dass Microsofts Implementierung 32-Bit unsigned long und 64-Bit size_t . Microsoft unterstützt "%zu" , aber diese Unterstützung wurde erst relativ spät hinzugefügt. Auf der anderen Seite, Gießen zu unsigned long ist nur dann ein Problem, wenn die jeweilige size_t Wert zufällig übersteigt ULONG_MAX was in der Praxis eher unwahrscheinlich ist.

Wenn Sie von einigermaßen modernen Implementierungen ausgehen können, verwenden Sie einfach "%zu" . Wenn Sie ältere Implementierungen berücksichtigen müssen, finden Sie hier ein absurd portables Programm, das sich an verschiedene Konfigurationen anpassen lässt:

#include <stdio.h>
#include <limits.h>
int main(void) {
    const size_t size = -1; /* largest value of type size_t */
#if __STDC_VERSION__ < 199901L
    if (size > ULONG_MAX) {
        printf("size is too big to print\n");
    }
    else {
        printf("old: size = %lu\n", (unsigned long)size);
    }
#else
    printf("new: size = %zu\n", size);
#endif
    return 0;
}

Eine Implementierung, die "Größe ist zu groß zum Drucken" ausgibt ( x86_64-w64-mingw32-gcc.exe -std=c90 unter Windows/Cygwin) unterstützt tatsächlich unsigned long long als eine Erweiterung von C90, so dass Sie vielleicht in der Lage sein werden, die Vorteile davon zu nutzen -- aber ich kann mir eine Implementierung aus der Zeit vor C99 vorstellen, die die unsigned long long unterstützt aber nicht "%llu" . Und diese Implementierung unterstützt "%zu" sowieso.

Ich habe die Erfahrung gemacht, dass ich nur drucken wollte size_t Werte in schnellem Wegwerfcode, wenn ich eine Implementierung erforsche, und nicht in Produktionscode. In einem solchen Kontext ist es wahrscheinlich ausreichend, einfach das zu tun, was funktioniert.

(Die Frage bezieht sich auf C, aber ich werde erwähnen, dass in C++ std::cout << sizeof (int) funktioniert in jeder Version der Sprache korrekt).

9voto

vulcan raven Punkte 31125

Eine Erweiterung der Antwort von Adam Rosenfield für Windows.

Ich habe diesen Code sowohl mit VS2013 Update 4 als auch mit VS2015 Preview 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 erzeugt binäre Ausgaben:

1
1
2

während die von VS2013 generierte Version sagt:

zu
zx
zd

Nota: ssize_t ist eine POSIX-Erweiterung und SSIZE_T ist eine ähnliche Sache in Windows-Datentypen daher habe ich hinzugefügt <BaseTsd.h> Hinweis.

Außerdem sind mit Ausnahme der folgenden C99/C11-Header alle C99-Header in der VS2015-Vorschau verfügbar:

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

Außerdem ist C11s <uchar.h> ist jetzt in der neuesten Vorschau enthalten.

Weitere Einzelheiten finden Sie hier alt とのことです。 neu Liste für Standardkonformität.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X