unsigned int vs. size_t

492

Zauważam, że współczesny kod C i C ++ wydaje się używać size_tzamiast int/ unsigned intprawie wszędzie - od parametrów funkcji łańcucha C po STL. Jestem ciekawy przyczyny tego i korzyści, jakie przynosi.

Obrabować
źródło

Odpowiedzi:

388

size_tTypem jest unsigned typu Liczba całkowita, która jest wynikiem sizeofoperatora (a offsetofoperatorem), więc na pewno będzie wystarczająco duży, aby zawierać wielkość największego obiektu system może obsłużyć (np statyczna tablica z 8GB).

size_tTyp może być większa niż, równa lub mniejsza od unsigned inti kompilator może przyjąć założenia o tym do optymalizacji.

Dokładniejsze informacje można znaleźć w standardzie C99, sekcja 7.17, którego wersja robocza jest dostępna w Internecie w formacie pdf , lub w standardzie C11, sekcja 7.19, dostępna również jako wersja pdf .

Remo.D
źródło
50
Nie. Pomyśl o x86-16 z dużym (niezbyt dużym) modelem pamięci: Wskaźniki są dalekie (32-bitowe), ale pojedyncze obiekty są ograniczone do 64k (więc size_t może być 16-bitowy).
dan04
8
„rozmiar największego obiektu” nie jest złym sformułowaniem, ale jest absolutnie poprawny. Szóstka obiektu może być znacznie bardziej ograniczona niż przestrzeń adresowa.
gnasher729
3
„Twój kompilator może założyć o tym”: Mam nadzieję, że kompilator zna dokładny zakres wartości, które size_tmogą reprezentować! Jeśli nie, to kto?
Marc van Leeuwen,
4
@Marc: Myślę, że chodziło bardziej o to, że kompilator może być w stanie coś zrobić z tą wiedzą.
8
Chciałbym tylko, aby ten coraz bardziej popularny typ nie wymagał dołączenia pliku nagłówka.
user2023370,
98

Klasyczny C (wczesny dialekt języka C opisany przez Briana Kernighana i Dennisa Ritchie w The C Programming Language, Prentice-Hall, 1978) nie zapewniał size_t. Komitet normalizacyjny C wprowadził w size_tcelu wyeliminowania problemu przenoszenia

Szczegółowo wyjaśnione na stronie embedded.com (z bardzo dobrym przykładem)

azeemarif
źródło
6
Kolejny świetny artykuł wyjaśniający zarówno size_t, jak i ptrdiff_t: viva64.com/en/a/0050
Ihor Kaharlichenko
73

Krótko mówiąc, size_tnigdy nie jest ujemny i maksymalizuje wydajność, ponieważ zostałby wpisany jako liczba całkowita bez znaku, która jest wystarczająco duża - ale nie za duża - aby reprezentować rozmiar możliwie największego obiektu na platformie docelowej.

Rozmiary nigdy nie powinny być ujemne i rzeczywiście size_tsą typem niepodpisanym. Ponadto, ponieważ size_tjest bez znaku, możesz przechowywać liczby, które są około dwa razy większe niż w odpowiednim typie ze znakiem, ponieważ możemy użyć bitu znaku do reprezentowania wielkości, podobnie jak wszystkich innych bitów w liczbie całkowitej bez znaku. Kiedy zyskujemy jeszcze jeden bit, mnożymy zakres liczb, które możemy reprezentować, około dwa razy.

Pytasz więc, dlaczego po prostu nie użyć unsigned int? Może nie być w stanie pomieścić wystarczająco dużych liczb. W implementacji, w której unsigned intjest 32 bity, największa liczba, jaką może reprezentować, to 4294967295. Niektóre procesory, takie jak IP16L32, mogą kopiować obiekty większe niż 4294967295bajty.

Pytasz więc, dlaczego nie użyć unsigned long int? Wymaga opłaty za wydajność na niektórych platformach. Standard C wymaga longzajmowania co najmniej 32 bitów. Platforma IP16L32 implementuje każdą 32-bitową długość jako parę 16-bitowych słów. Prawie wszyscy operatorzy 32-bitowi na tych platformach wymagają dwóch instrukcji, jeśli nie więcej, ponieważ pracują z 32 bitami w dwóch 16-bitowych porcjach. Na przykład przenoszenie 32-bitowej długości zwykle wymaga dwóch instrukcji maszyny - po jednej do przeniesienia każdej 16-bitowej porcji.

Używanie size_tpozwala uniknąć tego kosztu wydajności. Zgodnie z tym fantastycznym artykułem : „Type size_tto typedef, który jest aliasem dla jakiegoś typu liczb całkowitych bez znaku, zazwyczaj unsigned intlub unsigned long, ale być może nawet unsigned long long. Każda implementacja Standard C ma wybierać liczbę całkowitą bez znaku, która jest wystarczająco duża - ale nie większa niż potrzeba - reprezentować rozmiar największego możliwego obiektu na platformie docelowej. ”

Rose Perrone
źródło
1
Przepraszam, że skomentowałem to po tak długim czasie, ale po prostu musiałem potwierdzić największą liczbę, jaką może przechowywać niepodpisana int - być może źle rozumiem twoją terminologię, ale pomyślałem, że największa liczba, jaką może przechowywać nie podpisana int, to 4294967295, 65356 jest maksimum bez znaku zwarcia.
Mitch,
Jeśli twoja int bez znaku zajmuje 32 bity, to tak, największą liczbą, jaką może przechowywać, jest 2 ^ 32-1, czyli 4294967295 (0xffffffff). Czy masz inne pytanie?
Rose Perrone,
3
@Mitch: największa wartość, która może być reprezentowana w unsigned intpuszce i różni się w zależności od systemu. Wymagane jest co najmniej 65536 , ale często 4294967295i może być 18446744073709551615(2 ** 64-1) w niektórych systemach.
Keith Thompson
1
Największa wartość, jaką może zawierać 16-bitowy znak int to 65535, a nie 65536. Mała, ale ważna różnica, ponieważ 65536 jest taka sama jak 0 w 16-bitowym znaku int.
Sie Raybould,
1
@ gnasher729: Czy jesteś pewien standardu C ++? Po pewnym czasie mam wrażenie, że po prostu usunęli wszystkie absolutne gwarancje dotyczące zakresów liczb całkowitych (z wyłączeniem unsigned char). Standard nie wydaje się zawierać łańcucha „65535” ani „65536” nigdzie, a „+32767” występuje tylko (1,9: 9) w nucie jako możliwie największa liczba całkowita reprezentowana w int; nie ma żadnej gwarancji, że INT_MAXnie może być mniejsza!
Marc van Leeuwen,
51

Typ size_t to typ zwracany przez operator sizeof. Jest to liczba całkowita bez znaku, która jest w stanie wyrazić rozmiar w bajtach dowolnego zakresu pamięci obsługiwanego przez maszynę hosta. Jest (zazwyczaj) związany z ptrdiff_t, ponieważ ptrdiff_t jest liczbą całkowitą ze znakiem, taką, że sizeof (ptrdiff_t) i sizeof (size_t) są równe.

Pisząc kod C, zawsze powinieneś używać size_t, gdy masz do czynienia z zakresami pamięci.

Z drugiej strony typ int jest zasadniczo zdefiniowany jako wielkość (całkowitej) wartości (podpisanej), którą maszyna hosta może wykorzystać do najbardziej wydajnego wykonywania arytmetyki liczb całkowitych. Na przykład na wielu starszych komputerach typu PC wartość sizeof (size_t) wynosiłaby 4 (bajty), ale sizeof (int) wynosiłaby 2 (bajty). 16-bitowa arytmetyka była szybsza niż 32-bitowa arytmetyka, chociaż procesor mógł obsłużyć (logiczną) przestrzeń pamięci do 4 GiB.

Używaj typu int tylko wtedy, gdy zależy Ci na wydajności, ponieważ jej rzeczywista precyzja zależy w dużym stopniu zarówno od opcji kompilatora, jak i architektury maszyny. W szczególności standard C określa następujące niezmienniki: sizeof (char) <= sizeof (short) <= sizeof (int) <= sizeof (long) nie nakładając żadnych innych ograniczeń na rzeczywistą reprezentację precyzji dostępnej dla programisty dla każdego z te prymitywne typy.

Uwaga: NIE jest to to samo, co w Javie (która faktycznie określa precyzję bitową dla każdego typu „char”, „byte”, „short”, „int” i „long”).

Kevin S.
źródło
de facto definicja int jest taka, że ​​jest 16-bitowy na 16 maszynach i 32-bitowy na czymkolwiek większym. Napisano zbyt dużo kodu, który zakłada, że ​​int ma szerokość 32 bitów, aby to zmienić teraz, w wyniku czego ludzie powinni zawsze używać size_t lub {, u} int {8,16,32,64} _t, jeśli chcą czegoś konkretnego - - jako środek ostrożności ludzie powinni zawsze używać ich zamiast integralnych typów całkowitych.
Jaśniejsze
3
„Jest to liczba całkowita bez znaku, która może wyrażać rozmiar w bajtach dowolnego zakresu pamięci obsługiwanego na komputerze-hoście”. -> Nie. size_tJest w stanie reprezentować rozmiar dowolnego pojedynczego obiektu (np. Liczba, tablica, struktura). Cały zakres pamięci może przekroczyćsize_t
chux - Przywróć Monikę
„Pisząc kod C, zawsze powinieneś używać size_t, gdy masz do czynienia z zakresami pamięci.” - oznacza to, że każdy indeks do każdej tablicy powinien być size_t- Mam nadzieję, że nie masz tego na myśli. Przez większość czasu nie mamy do czynienia z tablicami, w których liczy się liczność przestrzeni adresowej + przenośność. W takich przypadkach weźmiesz size_t. W każdym innym przypadku bierzesz indeksy z (podpisanych) liczb całkowitych. Ponieważ zamieszanie (które pojawia się bez ostrzeżenia) wynikające z nieoczekiwanego niedomykania zachowania niepodpisanych jest częstsze i gorsze niż problemy z przenośnością, które mogą wystąpić w innych przypadkach.
johannes_lalala
23

Wpisz rozmiar_t musi być wystarczająco duży, aby przechowywać rozmiar dowolnego możliwego obiektu. Unsigned int nie musi spełniać tego warunku.

Na przykład w systemach 64-bitowych int i unsigned int mogą mieć szerokość 32 bitów, ale rozmiar_t musi być wystarczająco duży, aby przechowywać liczby większe niż 4G

Maciej Hehl
źródło
38
„obiekt” to język używany przez standard.
R .. GitHub ZATRZYMAJ LÓD
2
Myślę, że size_tmusiałoby być tak duże, gdyby kompilator mógł zaakceptować typ X taki, że sizeof (X) dałoby wartość większą niż 4G. Większość kompilatorów odrzuciłaby np. typedef unsigned char foo[1000000000000LL][1000000000000LL]A nawet foo[65536][65536];mogłaby zostać legalnie odrzucona, gdyby przekroczyła udokumentowany limit zdefiniowany w implementacji.
supercat
1
@MattJoiner: Brzmienie jest w porządku. „Obiekt” wcale nie jest niejasny, ale raczej zdefiniowany jako „region przechowywania”.
Wyścigi lekkości na orbicie
4

Ten fragment podręcznika glibc 0.02 może być również istotny podczas badania tematu:

Istnieje potencjalny problem z typem size_t i wersjami GCC przed wydaniem 2.4. ANSI C wymaga, aby size_t zawsze był typem bez znaku. W celu zapewnienia zgodności z plikami nagłówkowymi istniejących systemów GCC definiuje rozmiar_t w stddef.h' to be whatever type the system'ssys / types.h 'definiuje go jako. Większość systemów uniksowych, które definiują size_t w `sys / types.h ', definiują go jako typ podpisany. Niektóre kody w bibliotece zależą od tego, że size_t jest typem bez znaku i nie będzie działać poprawnie, jeśli zostanie podpisane.

Kod biblioteki GNU C, który oczekuje, że rozmiar_t będzie bez znaku, jest poprawny. Definicja size_t jako typu podpisanego jest niepoprawna. Planujemy, że w wersji 2.4 GCC zawsze zdefiniuje size_t jako typ bez znaku, a fixincludes' script will massage the system'ssys / types.h ', aby nie kolidować z tym.

W międzyczasie omawiamy ten problem, mówiąc GCC wprost, aby używał typu niepodpisanego dla size_t podczas kompilacji biblioteki GNU C. `config 'automatycznie wykryje, jakiego typu GCC używa dla size_t, aby go zastąpić, jeśli to konieczne.

Graeme Burke
źródło
2

Jeśli mój kompilator jest ustawiony na 32 bity, size_tnie jest niczym innym jak typedef dla unsigned int. Jeśli mój kompilator jest ustawiony na 64-bitowy, size_tnie jest niczym innym jak typedef dla unsigned long long.

Zebrafish
źródło
1
Może być zdefiniowane tak jak unsigned longdla obu przypadków w niektórych systemach operacyjnych.
StaceyGirl
-4

size_t to rozmiar wskaźnika.

Tak więc w 32 bitach lub wspólnym modelu ILP32 (liczba całkowita, długi, wskaźnik) size_t wynosi 32 bity. oraz w 64 bitach lub wspólny model LP64 (długi, wskaźnik) size_t wynosi 64 bity (liczby całkowite to nadal 32 bity).

Istnieją inne modele, ale są to te, których używa g ++ (przynajmniej domyślnie)


źródło
15
size_tniekoniecznie jest tego samego rozmiaru co wskaźnik, choć zwykle jest. Wskaźnik musi być w stanie wskazywać dowolne miejsce w pamięci; size_tmusi być wystarczająco duży, aby reprezentować rozmiar największego pojedynczego obiektu.
Keith Thompson