Jak wydrukować zmienną size_t przenośnie za pomocą rodziny printf?

403

Mam zmienną typu size_ti chcę ją wydrukować za pomocą printf(). Jakiego specyfikatora formatu używam do przenośnego drukowania?

W 32-bitowej maszynie %uwydaje się słuszna. Kompilowałem się g++ -g -W -Wall -Werror -ansi -pedantici nie było ostrzeżenia. Ale kiedy kompiluję ten kod na komputerze 64-bitowym, wyświetla ostrzeżenie.

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

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

Ostrzeżenie zniknie zgodnie z oczekiwaniami, jeśli zmienię to na %lu.

Pytanie brzmi: jak mogę napisać kod, aby kompilował się bez ostrzeżenia na komputerach 32- i 64-bitowych?

Edycja: Jako obejście, jedną z odpowiedzi może być „rzutowanie” zmiennej na liczbę całkowitą, która jest wystarczająco duża, powiedzmy unsigned longi drukuj za pomocą %lu. To działałoby w obu przypadkach. Szukam, czy jest jakiś inny pomysł.

Bieg
źródło
4
rzutowanie na unsigned longjest najlepszą opcją, jeśli twoja implementacja libc nie obsługuje zmodyfikatora; standard C99 zaleca, size_taby nie mieć rangi konwersji liczb całkowitych większych niż long, więc jesteś względnie bezpieczny
Christoph
możliwy duplikat niezależnych
maxschlepzig
1
Na platformie Windows rozmiar_t może być większy niż długi. Ze względu na kompatybilność długi jest zawsze 32-bitowy, ale size_t może być 64-bitowy. Tak więc rzutowanie na niepodpisany długi może stracić połowę bitów. Przepraszam :-)
Bruce Dawson,

Odpowiedzi:

481

Użyj zmodyfikatora:

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
źródło
7
+1. Czy to dodatek do C99, czy dotyczy to również C ++ (nie mam pod ręką C90)?
avakar
6
jest to dodatek do C99 i nie znajduje się na liście printf()modyfikatorów długości wersji C ++ 0x z 2009-11-09 (tabela 84 na stronie 672)
Christoph
3
@Christoph: Nie ma go również w najnowszym projekcie, n3035.
GManNickG
11
@avakar @Adam Rosenfield @Christoph @GMan: Jednak w n3035 § 1.2 Odniesienia normatywne, odnosi się tylko do standardu C99, a § 17.6.1.2 / 3 tych samych stanów „Udostępniono funkcje biblioteki standardowej C”. Zinterpretowałbym to w ten sposób, że jeśli nie określono inaczej, wszystko w standardowej bibliotece C99 jest częścią standardowej biblioteki C ++ 0x, w tym dodatkowe specyfikatory formatu w C99.
James McNellis
9
@ArunSaha: Jest to funkcja tylko C99, a nie C ++. Jeśli chcesz, aby się kompilował -pedantic, musisz albo uzyskać kompilator obsługujący wersję roboczą C ++ 1x (bardzo mało prawdopodobne), albo musisz przenieść kod do pliku skompilowanego jako C99. W przeciwnym razie jedyną opcją jest rzutowanie zmiennych unsigned long longi używanie ich, %lluaby były maksymalnie przenośne.
Adam Rosenfield
88

Wygląda na to, że różni się w zależności od używanego kompilatora (blech):

... i oczywiście, jeśli używasz C ++, możesz użyć coutzamiast tego, jak sugeruje AraK .

TJ Crowder
źródło
3
zjest również wspierany przez newlib (tj. cygwin)
Christoph
7
%zdjest niepoprawny dla size_t; jest poprawny dla typu podpisanego odpowiadającego size_t, ale size_tsam jest typem niepodpisanym.
Keith Thompson
1
@KeithThompson: Wspomniałem %zurównież (i %zxna wypadek, gdyby chcieli hex). To prawda, że %zuprawdopodobnie powinien być pierwszy na liście. Naprawiony.
TJ Crowder
10
@TJCrowder: Nie sądzę, że %zdpowinien być w ogóle na liście. Nie mogę wymyślić żadnego powodu do użycia %zdzamiast %zudrukowania size_twartości. To nie jest nawet poprawne (ma niezdefiniowane zachowanie), jeśli wartość przekracza SIZE_MAX / 2. (Dla kompletności możesz wymienić %zoósemkowy.)
Keith Thompson
2
@FUZxxl: POSIX nie wymaga tego , ssize_taby odpowiadał podpisanemu typowi size_t, więc nie można zagwarantować zgodności "%zd". ( Prawdopodobnie jest na większości implementacji.) Pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
Keith Thompson
59

W przypadku C89 użyj %lui rzutuj wartość na unsigned long:

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

W przypadku C99 i nowszych użyj %zu:

size_t foo;
...
printf("foo = %zu\n", foo);
John Bode
źródło
7
Biorąc pod uwagę rok 2013, zasugeruj „Dla C99 i późniejszych” oraz „Dla C99 wcześniejszych”. Najlepsza odpowiedź.
chux - Przywróć Monikę
8
Nie rób tego. Nie powiedzie się w 64-bitowym systemie Windows, gdzie size_t to 64 bity, a długi to 32 bity.
Yttrill,
1
@Yttrill: Jaka jest zatem odpowiedź na 64-bitowe okna?
John Bode,
1
@JohnBode Być może unsigned long long?
James Ko
2
Lub: możesz rzutować na a, uint64_ta następnie użyć PRIu64makra z inttypes.h, który zawiera specyfikator formatu.
James Ko
9

Rozszerzenie odpowiedzi Adama Rosenfielda dla systemu Windows.

Testowałem ten kod zarówno w wersji VS2013 Update 4, jak i w wersji zapoznawczej VS2015:

// 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;
}

Generowane przez binarne wyjścia VS2015:

1
1
2

podczas gdy ten wygenerowany przez VS2013 mówi:

zu
zx
zd

Uwaga: ssize_tjest rozszerzeniem POSIX i SSIZE_Tjest podobna w typach danych systemu Windows , dlatego dodałem <BaseTsd.h>odwołanie.

Dodatkowo, z wyjątkiem następujących nagłówków C99 / C11, wszystkie nagłówki C99 są dostępne w wersji zapoznawczej VS2015:

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

Ponadto C11 <uchar.h>jest teraz uwzględnione w najnowszej wersji zapoznawczej.

Aby uzyskać więcej informacji, zobacz tę starą i nową listę standardowej zgodności.

kruk wulkaniczny
źródło
Aktualizacja VS2013 5 daje takie same wyniki, jak aktualizacja 4.
Nathan Kidd,
6

Dla tych, którzy mówią o robieniu tego w C ++, który niekoniecznie obsługuje rozszerzenia C99, serdecznie polecam format :: ::. To sprawia, że ​​pytanie size size rozmiar size może być dyskusyjne:

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

Ponieważ nie potrzebujesz specyfikatorów rozmiaru w formacie boost ::, możesz się martwić, jak chcesz wyświetlić wartość.

swestrup
źródło
4
Prawdopodobnie więc chcę %u.
GManNickG
5
std::size_t s = 1024;
std::cout << s; // or any other kind of stream like stringstream!
ARAK
źródło
8
Tak, ale pytający pyta konkretnie o printfspecyfikator. Sądzę, że mają pewne inne nieokreślone ograniczenia, które sprawiają, że używanie std::coutproblemu.
Donal Fellows
1
@Donal Zastanawiam się, jaki problem mogą tworzyć strumienie C ++ w projekcie C ++!
AraK
9
@AraK. Są bardzo wolne? Dodają DUŻO bajtów bez powodu. ArunSaha chce po prostu wiedzieć ze względu na swoją własną wiedzę? Osobiste preferencje (wolę stdio od fstream siebie). Jest wiele powodów.
KitsuneYMG
1
@TKCrowder: Cóż, w pierwotnym żądaniu było powiedziane, że pożądane jest rozwiązanie C (poprzez tagowanie) i istnieją dobre powody, aby nie używać strumieni w C ++, np. Jeśli deskryptor formatu wyjściowego jest pobierany z katalogu komunikatów. (Możesz napisać parser dla wiadomości i użyć strumieni, jeśli chcesz, ale to dużo pracy, kiedy możesz po prostu wykorzystać istniejący kod.)
Donal Fellows
1
@Donal: Tagi to C i C ++. Nie jestem w żaden sposób zwolennikiem przesyłania strumieniowego we / wy C ++ (nie jestem jego fanem), po prostu wskazuję, że pytanie nie pierwotnie * „... zapytaj o printfspecyfikację”.
TJ Crowder
5
printf("size = %zu\n", sizeof(thing) );
nategoose
źródło
2

Jak powiedział AraK, interfejs strumieni c ++ zawsze będzie działał przenośnie.

std :: size_t s = 1024; std :: cout << s; // lub jakikolwiek inny strumień, taki jak stringstream!

Jeśli chcesz C stdio, nie ma przenośnej odpowiedzi na to pytanie w niektórych przypadkach „przenośnych”. I robi się brzydko, ponieważ, jak widzieliście, wybranie flag w niewłaściwym formacie może wygenerować ostrzeżenie kompilatora lub dać niepoprawne dane wyjściowe.

C99 próbował rozwiązać ten problem za pomocą formatów inttypes.h, takich jak „%” PRIdMAX ”\ n”. Ale podobnie jak w przypadku „% zu”, nie każdy obsługuje c99 (jak MSVS przed 2013 rokiem). Istnieją pliki „msinttypes.h” pływające wokół, aby sobie z tym poradzić.

Jeśli wybierzesz inny typ, w zależności od flag możesz otrzymać ostrzeżenie kompilatora o obcięciu lub zmianie znaku. Jeśli wybierzesz tę trasę, wybierz większy odpowiedni stały rozmiar. Jeden z niepodpisanych długich długich i „% llu” lub niepodpisanych długich „% lu” powinien działać, ale llu może również spowolnić rzeczy w 32-bitowym świecie jako zbyt dużym. (Edycja - mój Mac wyświetla ostrzeżenie w 64-bitowej wersji, jeśli% llu nie pasuje do size_t, mimo że% lu,% llu i size_t są tego samego rozmiaru. A% lu i% llu nie są tego samego rozmiaru na moim MSVS2012. być może będziesz musiał przesłać + użyć formatu, który pasuje.)

W tym przypadku możesz przejść ze stałymi typami rozmiarów, takimi jak int64_t. Ale poczekaj! Teraz wróciliśmy do c99 / c ++ 11, a starszy MSVS ponownie zawiedzie. Dodatkowo masz również casty (np. Map.size () nie jest stałym typem rozmiaru)!

Możesz użyć nagłówka lub biblioteki innej firmy, takiej jak boost. Jeśli jeszcze go nie używasz, możesz nie chcieć zawyżać swojego projektu w ten sposób. Jeśli chcesz dodać jeden tylko dla tego problemu, dlaczego nie użyć strumieni c ++ lub kompilacji warunkowej?

Więc sprowadzasz się do strumieni c ++, kompilacji warunkowej, frameworków zewnętrznych lub czegoś w rodzaju przenośnego urządzenia, które akurat działa.

Rick Berge
źródło
-1

Czy ostrzeże Cię, jeśli przekażesz 32-bitową liczbę całkowitą bez znaku do formatu% lu? Powinno być dobrze, ponieważ konwersja jest dobrze zdefiniowana i nie traci żadnych informacji.

Słyszałem, że niektóre platformy definiują makra <inttypes.h>, które można wstawić do literału ciągu formatu, ale nie widzę tego nagłówka w moim kompilatorze Windows C ++, co oznacza, że ​​może nie być wieloplatformowy.

Kylotan
źródło
1
Większość kompilatorów nie ostrzeże Cię, jeśli przekażesz coś o złym rozmiarze do printf. GCC jest wyjątkiem. inttypes.h został zdefiniowany w C99, więc każdy kompilator C, który jest zgodny z C99, będzie go miał, co powinno już być wszystkim. Może być jednak konieczne włączenie C99 przy użyciu flagi kompilatora. W każdym razie intttypes.h nie definiuje konkretnego formatu dla size_t lub ptrdiff_t, ponieważ zdecydowano, że są wystarczająco ważne, aby uzyskać własne specyfikatory wielkości odpowiednio „z” i „t”.
swestrup
Jeśli używasz %lu, powinieneś rzucić size_twartość na unsigned long. Nie ma niejawnej konwersji (innej niż promocje) dla argumentów na printf.
Keith Thompson
-2

C99 definiuje w tym celu „% zd” itp. (dzięki komentujących) nie istnieje przenośny format specifier za to w C ++ - Państwo mogli używać %p, które woulkd słowo w tych dwóch scenariuszy, ale nie jest przenośnym wybór albo i daje wartość w Hex.

Możesz też użyć transmisji strumieniowej (np. Stringstream) lub bezpiecznej wymiany printf, takiej jak Boost Format . Rozumiem, że ta rada ma jedynie ograniczone zastosowanie (i wymaga C ++). (Zastosowaliśmy podobne podejście dostosowane do naszych potrzeb przy wdrażaniu obsługi Unicode).

Podstawowym problemem dla C jest to, że printf wykorzystujący wielokropek jest z założenia niebezpieczny - musi określić dodatkowy rozmiar argumentu na podstawie znanych argumentów, więc nie można go naprawić, aby obsługiwał „cokolwiek masz”. Jeśli więc kompilator nie zaimplementuje zastrzeżonych rozszerzeń, nie będziesz miał szczęścia.

peterchen
źródło
2
zmodidfier Rozmiar jest standardowy C, ale niektóre libc implementacje tkwią w 1990 roku z różnych powodów (np Microsoft zasadzie opuszczonym C na rzecz C ++ i - od niedawna - C #)
Christoph
3
C99 zdefiniował specyfikator wielkości „z” jako rozmiar wartości size_t, a „t” jako rozmiar wartości ptrdiff_t.
swestrup
2
%zdjest źle, jest niepodpisane, więc powinno być %zu.
David Conrad
-3

Na niektórych platformach i dla niektórych typów dostępne są określone specyfikatory konwersji printf, ale czasami trzeba uciekać się do rzutowania na większe typy.

Udokumentowałem tutaj ten podchwytliwy problem na przykładowym kodzie: http://www.pixelbeat.org/programming/gcc/int_types/ i okresowo aktualizowałem go o informacje na temat nowych platform i typów.

pixelbeat
źródło
1
Zwróć uwagę, że odradzane są odpowiedzi tylko do linku, więc odpowiedzi SO powinny być punktem końcowym poszukiwania rozwiązania (w porównaniu z kolejnym postojem referencji, które z czasem stają się nieaktualne). Rozważ dodanie tutaj autonomicznego streszczenia, zachowując link jako odniesienie.
kleopatra
-5

jeśli chcesz wydrukować wartość size_t jako ciąg, możesz to zrobić:

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);

wynikiem jest:

numer: 2337200120702199116

tekst: Chodźmy łowić ryby zamiast siedzieć na naszym ale !!

Edycja: ponowne przeczytanie pytania z powodu zmniejszonej liczby głosów zauważyłem, że jego problemem nie jest% llu lub% I64d, ale typ size_t na różnych komputerach zobacz to pytanie https://stackoverflow.com/a/918909/1755797
http: // www. cplusplus.com/reference/cstdio/printf/

size_t jest bez znaku int na maszynie 32-bitowej i bez znaku long long int na 64-bit,
ale% ll zawsze oczekuje bez znaku long long int.

size_t różni się długością w różnych systemach operacyjnych, podczas gdy% llu jest taki sam

Andre
źródło
4
Co to za bzdury ?!
Antti Haapala
rzutowanie pierwszych 8 bajtów tablicy char do niepodpisanego długiego, 64-bitowego wskaźnika przez wskaźnik size_t i wydrukowanie ich jako liczby za pomocą printf% I64d nie jest naprawdę spektakularne, wiem, oczywiście, że nie miałem kodu, aby zapobiec przepełnieniu typu, ale to nie wchodzi w zakres pytania.
Andre