Co to jest size_t w C?

626

Mylę się z size_tC. Wiem, że sizeofoperator zwraca go . Ale co to właściwie jest? Czy to typ danych?

Powiedzmy, że mam forpętlę:

for(i = 0; i < some_size; i++)

Czy powinienem użyć int i;lub size_t i;?

Vijay
źródło
11
Jeśli są to jedyne opcje, użyj opcji intif some_sizejest podpisane, size_tjeśli nie jest podpisane.
Nate
8
@Nate To jest nieprawidłowe. POSIX ma typ ssize_t, ale faktycznie poprawnym typem jest ptrdiff_t.
Steven Stewart-Gallus
2
Odpowiedzi nie są tak jasne, jak w przypadku programowania niskiego poziomu: C, asemblacja i wykonywanie programu na Intel® 64 . Jak stwierdzono w książce, użycie indeksu int imoże nie wystarczyć do rozwiązania ogromnej tablicy. Dzięki temu size_t imożesz zająć się większą liczbą indeksów, więc nawet jeśli masz ogromną tablicę, nie powinno to stanowić problemu. size_tjest typem danych: zwykle a, unsigned long intale zależy to od twojego systemu.
bruno

Odpowiedzi:

461

Z Wikipedii :

Zgodnie ze standardem ISO C 1999 (C99), size_tjest liczbą całkowitą bez znaku o długości co najmniej 16 bitów (patrz sekcje 7.17 i 7.18.3).

size_tjest niepodpisanym typem danych zdefiniowanym przez kilka standardów C / C ++, np. C99 ISO / IEC 9899, ​​który jest zdefiniowany w stddef.h. 1 Można go dodatkowo zaimportować, dołączając, stdlib.hjak ten plik wewnętrznie zawiera stddef.h.

Ten typ służy do reprezentowania wielkości obiektu. Funkcje biblioteczne, które przyjmują lub zwracają rozmiary, oczekują, że będą typu lub mają typ zwracany size_t. Ponadto najczęściej używany rozmiar operatora oparty na kompilatorze powinien być oceniany na stałą, zgodną z wartością size_t.

Implikacją size_tjest typ, który może przechowywać dowolny indeks tablicy.

sblom
źródło
4
„Funkcje biblioteczne, które przyjmują lub zwracają rozmiary, oczekują, że będą typu… size_t” Z wyjątkiem tego, że stat () używa off_t dla rozmiaru pliku
Draemon
64
@Draemon Ten komentarz odzwierciedla podstawowe zamieszanie. size_tjest dla obiektów w pamięci. Standard C nawet nie definiuje stat()lub off_t(są to definicje POSIX) ani nie ma nic wspólnego z dyskami lub systemami plików - zatrzymuje się na FILEstrumieniach. Zarządzanie pamięcią wirtualną różni się całkowicie od systemów plików i zarządzania plikami, jeśli chodzi o wymagania dotyczące wielkości, dlatego wzmianka o tym nie off_tma znaczenia.
jw013,
3
@ jw013: Nie nazwałbym tego fundamentalnym zamieszaniem, ale robisz interesujący punkt. Jednak cytowany tekst nie mówi „rozmiary obiektów w pamięci”, a „offset” nie jest dobrą nazwą dla typu rozmiaru, niezależnie od tego, gdzie jest przechowywany.
Draemon
30
@Draemon Dobra uwaga. Ta odpowiedź cytuje Wikipedię, która w tym przypadku nie ma najlepszego wyjaśnienia. Sam standard C jest znacznie wyraźniejszy: definiuje size_tjako typ wyniku sizeofoperatora (około 7.17p2 <stddef.h>). Sekcja 6.5 wyjaśnia dokładnie, jak działają wyrażenia C (dla 6.5.3.4 sizeof). Ponieważ nie można zastosować sizeofdo pliku dyskowego (głównie dlatego, że C nawet nie definiuje działania dysków i plików), nie ma miejsca na zamieszanie. Innymi słowy, obwiniaj Wikipedię (i tę odpowiedź za cytowanie Wikipedii, a nie faktycznego standardu C).
jw013,
2
@Draemon - Zgodziłbym się również z oceną „fundamentalnego zamieszania”. Jeśli nie czytałeś standardów C / C ++, możesz pomyśleć, że „obiekt” odnosi się do „programowania obiektowego”, czego nie robi. Przeczytaj standard C, który nie ma żadnego z tych obiektów OOP, ale jeszcze ma obiekty, i dowiedz się. Odpowiedź może cię zaskoczyć!
Heath Hunnicutt
220

size_tjest typem bez znaku. Dlatego nie może reprezentować żadnych wartości ujemnych (<0). Używasz go, gdy coś liczysz, i masz pewność, że nie może to być ujemne. Na przykład strlen()zwraca a, size_tponieważ długość ciągu musi wynosić co najmniej 0.

W twoim przykładzie, jeśli indeks pętli będzie zawsze większy od 0, warto użyć size_tdowolnego innego typu danych bez znaku.

Kiedy używasz size_tobiektu, musisz upewnić się, że we wszystkich kontekstach, w których jest on używany, w tym w arytmetyce, potrzebujesz wartości nieujemnych. Załóżmy na przykład, że masz:

size_t s1 = strlen(str1);
size_t s2 = strlen(str2);

i chcesz znaleźć różnicę długości str2i str1. Nie możesz zrobić:

int diff = s2 - s1; /* bad */

Wynika to z faktu, że przypisana wartość diffzawsze będzie liczbą dodatnią, nawet gdy s2 < s1, ponieważ obliczenia są wykonywane dla typów niepodpisanych. W takim przypadku, w zależności od tego, jaki jest twój przypadek użycia, możesz lepiej użyć int(lub long long) dla s1i s2.

W C / POSIX jest kilka funkcji, które mogłyby / powinny być używane size_t, ale nie z powodów historycznych. Na przykład drugi parametr fgetspowinien być idealnie size_t, ale jest int.

Alok Singhal
źródło
8
@Alok: Dwa pytania: 1) jaki jest rozmiar size_t? 2) dlaczego miałbym preferować size_tcoś takiego unsigned int?
Lazer
2
@Lazer: rozmiar size_tto sizeof(size_t). Standardowa gwarancja C, która SIZE_MAXbędzie wynosić co najmniej 65535. size_tjest typem zwracanym przez sizeofoperatora i jest używana w standardowej bibliotece (na przykład strlenzwroty size_t). Jak powiedział Brendan, size_tnie musi być taki sam jak unsigned int.
Alok Singhal
4
@Lazer - tak, size_tz pewnością jest typem bez znaku.
Alok Singhal
2
@Celeritas nie, mam na myśli, że niepodpisany typ może reprezentować tylko wartości nieujemne. Prawdopodobnie powinienem powiedzieć: „To nie może reprezentować wartości ujemnych”.
Alok Singhal
4
@JasonOster, uzupełnienie dwóch nie jest wymagane w standardzie C. Jeśli wartość s2 - s1przepełnienia an int, zachowanie jest niezdefiniowane.
Alok Singhal,
73

size_t jest typem, który może przechowywać dowolny indeks tablicy.

W zależności od implementacji może to być dowolna z następujących opcji:

unsigned char

unsigned short

unsigned int

unsigned long

unsigned long long

Oto, jak size_tzdefiniowano w stddef.hmojej maszynie:

typedef unsigned long size_t;
Arjun Sreedharan
źródło
4
Z pewnością typedef unsigned long size_tzależy od kompilatora. A może sugerujesz, że tak jest zawsze?
chux - Przywróć Monikę
4
@chux: Rzeczywiście, tylko dlatego, że jedna implementacja definiuje to jako takie, nie oznacza, że ​​wszyscy tak robią. Przykład: 64-bitowy system Windows. unsigned longjest 32-bitowy, size_tjest 64-bitowy
Tim Čas
2
jaki jest dokładnie cel size_t? Kiedy mogę utworzyć dla siebie zmienną, taką jak: „int mysize_t;” lub „long mysize_t” lub „unsigned long mysize_t”. Dlaczego ktoś miałby dla mnie utworzyć tę zmienną?
midkin
1
@midkin size_tnie jest zmienną. Jest to typ, którego możesz użyć, gdy chcesz reprezentować rozmiar obiektu w pamięci.
Arjun Sreedharan
1
czy to prawda, że size_tna 32-bitowej maszynie zawsze jest 32 bity, podobnie 64 bity?
John Wu
70

Jeśli jesteś typem empirycznym ,

echo | gcc -E -xc -include 'stddef.h' - | grep size_t

Wyjście dla Ubuntu 14.04 64-bit GCC 4.8:

typedef long unsigned int size_t;

Należy pamiętać, że stddef.hjest udostępniany przez GCC, a nie glibc src/gcc/ginclude/stddef.hw GCC 4.2.

Ciekawe występy w C99

  • mallocprzyjmuje size_tjako argument, więc określa maksymalny rozmiar, jaki można przydzielić.

    A ponieważ jest on zwracany przez sizeof, myślę, że ogranicza maksymalny rozmiar dowolnej tablicy.

    Zobacz także: Jaki jest maksymalny rozmiar tablicy w C?

Ciro Santilli
źródło
1
Mam to samo środowisko, jednak przetestowałem je pod kątem 32 bitów, z pominięciem opcji „-m32” GCC, wynik był następujący: „typedef unsigned int size_t”. Dzięki za udostępnienie tego niesamowitego polecenia @Ciro, bardzo mi pomogło! :-)
silvioprog
2
Sama sprawa nie jest myląca. To zagubiony umysł próbuje zadawać wiele pytań i udzielać wielu odpowiedzi. Dziwi mnie, że ta odpowiedź i ta Arjun Sreedharan nadal nie powstrzymują ludzi przed pytaniem i udzielaniem odpowiedzi.
biocyberman
1
Świetna odpowiedź, ponieważ tak naprawdę mówi ci, co size_tjest , przynajmniej w popularnej dystrybucji Linuksa.
Andrey Portnoy
25

Strona man dla types.h mówi:

size_t będzie liczbą całkowitą bez znaku

kodaddict
źródło
19

Ponieważ nikt jeszcze o tym nie wspominał, głównym znaczeniem językowym size_tjest to, że sizeofoperator zwraca wartość tego typu. Podobnie, głównym znaczeniem ptrdiff_tjest to, że odjęcie jednego wskaźnika od drugiego da wartość tego typu. Funkcje biblioteczne, które to akceptują, robią to, ponieważ pozwoli to takim funkcjom na pracę z obiektami, których rozmiar przekracza UINT_MAX w systemach, w których takie obiekty mogłyby istnieć, bez zmuszania dzwoniących do marnowania kodu, przekazując wartość większą niż „unsigned int” w systemach, w których większy typ wystarczyłby dla wszystkich możliwych przedmiotów.

supercat
źródło
Moje pytanie zawsze brzmiało: jeśli sizeof nigdy nie istniał, czy istnieje potrzeba size_t?
Dziekan P
@DeanP: Być może nie, choć pojawiłoby się pytanie, jakiego typu argumentu należy użyć do takich rzeczy malloc(). Osobiście wolałbym widzieć wersje, które przyjmują argumenty typu int, longa long longniektóre implementacje promują krótsze typy, a inne implementują np. lmalloc(long n) {return (n < 0 || n > 32767) ? 0 : imalloc(n);}[Na niektórych platformach, dzwonienie do imalloc(123)byłoby tańsze niż dzwonienie lmalloc(123);, a nawet na platformie, na której size_tjest 16 bity, kod, który chce przydzielić rozmiar obliczony jako wartość „długa” ...
supercat
... powinien być w stanie polegać na niepowodzeniu alokacji, jeśli wartość jest większa niż jest w stanie obsłużyć alokator.
supercat
11

Aby size_tdowiedzieć się, dlaczego musi istnieć i jak się tu dostaliśmy:

W kategoriach pragmatycznych size_ti ptrdiff_tsą gwarantowane do 64 bitów szerokości na realizację 64-bitowej, 32 bity szeroki na realizację 32-bitowej, i tak dalej. Nie mogli zmusić żadnego istniejącego typu, aby oznaczał to w każdym kompilatorze bez zerwania starszego kodu.

A size_tlub ptrdiff_tniekoniecznie jest taki sam jak intptr_tlub uintptr_t. Byli różni się w niektórych architekturach, które były nadal w użyciu, kiedy size_ti ptrdiff_tzostały dodane do standardowego w późnych latach 80., kiedy stają się przestarzałe i C99 dodano wiele nowych typów, ale jeszcze nie przeszły (takie jak 16-bitowym systemie Windows). 16-bitowy tryb chroniony x86 miał pamięć segmentową, w której największa możliwa tablica lub struktura mogła mieć jedynie 65 536 bajtów, ale farwskaźnik musiał mieć szerokość 32 bitów, więcej niż rejestry. Na nich intptr_tmiałby 32 bity szerokości, ale size_tiptrdiff_tmoże mieć 16 bitów szerokości i zmieścić się w rejestrze. A kto wiedział, jaki system operacyjny może zostać napisany w przyszłości? Teoretycznie architektura i386 oferuje 32-bitowy model segmentacji z 48-bitowymi wskaźnikami, których żaden system operacyjny nigdy nie używał.

Typ przesunięcia pamięci nie może być, longponieważ o wiele za dużo starszego kodu zakłada się, że longma on dokładnie 32 bity. To założenie zostało nawet wbudowane w interfejsy API UNIX i Windows. Niestety, wiele innych starszych kodów również zakładało, że a longjest wystarczająco szerokie, aby pomieścić wskaźnik, przesunięcie pliku, liczbę sekund, które upłynęły od 1970 roku i tak dalej. POSIX zapewnia teraz ustandaryzowany sposób na wymuszenie, aby to drugie założenie było prawdziwe zamiast pierwszego, ale nie jest to również przenośne założenie.

Nie mogło być tak, intponieważ tylko niewielka garść kompilatorów w latach 90. miała int64 bity szerokości. Potem naprawdę się dziwili, utrzymując longszerokość 32 bitów. Kolejna wersja Standardu uznała, że ​​jest niezgodna z prawem, ponieważ intjest szersza niż long, ale intnadal ma 32 bity szerokości w większości systemów 64-bitowych.

Nie mogło być long long int, co zresztą zostało dodane później, ponieważ zostało utworzone tak, aby miało szerokość co najmniej 64 bitów, nawet w systemach 32-bitowych.

Potrzebny był więc nowy typ. Nawet gdyby tak nie było, wszystkie inne typy oznaczały coś innego niż przesunięcie w tablicy lub obiekcie. A jeśli była jedna lekcja z fiasku migracji od 32 do 64 bitów, to musiała być konkretna, jakie właściwości powinien mieć typ, a nie używać takiej, która oznaczała różne rzeczy w różnych programach.

Davislor
źródło
Nie zgadzam się z „ size_ti ptrdiff_tgwarantujemy 64-bitową szerokość w 64-bitowej implementacji” itp. Gwarancja jest zawyżona. Zasięg size_tzależy przede wszystkim od pojemności pamięci implementacji. „Implementacja n-bitowa” to przede wszystkim natywna szerokość procesora liczb całkowitych. Z pewnością wiele implementacji używa pamięci o podobnej wielkości i szerokości szyny procesora, ale istnieją szerokie natywne liczby całkowite ze skąpą pamięcią lub wąskie procesory z dużą ilością pamięci, które rozróżniają te dwie właściwości implementacji.
chux - Przywróć Monikę
8

size_ti intnie są wymienne. Na przykład w 64-bitowym systemie Linux size_tma rozmiar 64-bitowy (tj. sizeof(void*)), Ale intma 32-bit.

Zauważ też, że size_tjest niepodpisany. Jeśli potrzebujesz podpisanej wersji, jest ona dostępna ssize_tna niektórych platformach i byłaby bardziej odpowiednia dla Twojego przykładu.

Jako ogólną zasadę sugerowałbym stosowanie intw większości ogólnych przypadków i używanie size_t/ tylko ssize_twtedy, gdy jest to szczególnie potrzebne ( mmap()na przykład).

dtoux
źródło
3

Ogólnie rzecz biorąc, jeśli zaczynasz od zera i idziesz w górę, zawsze używaj typu bez znaku, aby uniknąć przepełnienia prowadzącego do sytuacji ujemnej wartości. Jest to niezwykle ważne, ponieważ jeśli granice tablicy są mniejsze niż maksimum pętli, ale maksimum pętli okazuje się większe niż maksimum twojego typu, obejmiesz wartość ujemną i może wystąpić błąd segmentacji (SIGSEGV ). Ogólnie rzecz biorąc, nigdy nie używaj int dla pętli zaczynającej się od 0 i idącej w górę. Użyj niepodpisanego.

znak
źródło
3
Nie mogę zaakceptować twojej argumentacji. Mówisz, że lepiej, aby błąd przepełnienia po cichu prowadził do uzyskania dostępu do prawidłowych danych w Twojej tablicy?
maf-soft
1
@ maf-soft jest poprawny. jeśli błąd nie zostanie wykryty, pogorszy się to po awarii programu. dlaczego ta odpowiedź ma pozytywne opinie?
yoyo_fun
Jeśli uzyskuje dostęp do prawidłowych danych w tablicy, to nie jest to błąd, ponieważ typ niepodpisany nie przepełni się przy limicie typu podpisanego. Czym jest ta logika? Powiedzmy, że z jakiegoś powodu używasz char do iteracji ponad 256-elementowej tablicy ... podpisany przepełni się przy 127, a 128-ty element sigsegv, ale jeśli użyjesz niepodpisanego, przejdzie przez całą tablicę zgodnie z przeznaczeniem. Z drugiej strony, gdy używasz int, twoje tablice nie będą tak naprawdę większe niż 2 miliardy elementów, więc w każdym razie nie ma to znaczenia ...
Purple Ice
1
Nie wyobrażam sobie żadnej sytuacji, w której przepełnienie liczb całkowitych nie jest błędem, niezależnie od tego, czy otacza ono wartość dodatnią, czy ujemną. To, że nie dostaniesz błędu, nie oznacza, że ​​widzisz prawidłowe zachowanie! I możesz doświadczyć błędu segmentacji, czy nie, niezależnie od tego, czy przesunięcie jest dodatnie czy ujemne; wszystko zależy od układu pamięci. @ Purpurowy, nie sądzę, że mówisz to samo, co ta odpowiedź; twój argument wydaje się być taki, że powinieneś wybrać typ danych wystarczająco duży, aby pomieścić największą wartość, którą chcesz w nim umieścić, co jest po prostu zdrowym rozsądkiem.
Soren Bjornstad
To powiedziawszy, wolę używać semantycznie indeksów pętli bez znaku ; jeśli twoja zmienna nigdy nie będzie ujemna, równie dobrze możesz wskazać to w wybranym typie. Mogłoby to również pozwolić kompilatorowi wykryć błąd, w którym wartość zakończyła się wartością ujemną, chociaż GCC przynajmniej jest dość okropna w wykrywaniu tego konkretnego błędu (raz zainicjowałem niepodpisany na -1 i nie dostałem ostrzeżenia). Podobnie size_t jest semantycznie odpowiedni dla indeksów tablicowych.
Soren Bjornstad
3

size_t jest liczbą całkowitą bez znaku. W systemach korzystających z Biblioteki GNU C będzie to unsigned int lub unsigned long int. size_t jest powszechnie używany do indeksowania tablic i zliczania pętli.

Książę
źródło
1

size_t lub dowolny niepodpisany typ może być postrzegany jako zmienna pętli, ponieważ zmienne pętli są zwykle większe lub równe 0.

Kiedy używamy obiektu size_t , musimy upewnić się, że we wszystkich kontekstach jest on używany, w tym arytmetyczny, chcemy tylko wartości nieujemnych. Na przykład następujący program zdecydowanie dałby nieoczekiwany wynik:

// C program to demonstrate that size_t or
// any unsigned int type should be used 
// carefully when used in a loop

#include<stdio.h>
int main()
{
const size_t N = 10;
int a[N];

// This is fine
for (size_t n = 0; n < N; ++n)
a[n] = n;

// But reverse cycles are tricky for unsigned 
// types as can lead to infinite loop
for (size_t n = N-1; n >= 0; --n)
printf("%d ", a[n]);
}

Output
Infinite loop and then segmentation fault
bishwas pokharel
źródło
1

size_tjest typem danych całkowitych bez znaku, który może przypisać tylko 0 i więcej niż 0 wartości całkowitych. Mierzy bajty dowolnej wielkości obiektu i zwracane przez sizeofoperatora. constjest reprezentacją składni size_t, ale bez constciebie możesz uruchomić program.

const size_t number;

size_tregularnie używane do indeksowania tablic i zliczania pętli. Jeśli jest 32-bitto kompilator , działałoby unsigned int. Jeśli jest 64-bitto kompilator , działałoby to unsigned long long intrównież. Tam dla maksymalnego rozmiaru w size_tzależności od typu kompilatora.

size_tjuż na zdefiniowanie <stdio.h>pliku nagłówka, ale można również zdefiniować przez <stddef.h>, <stdlib.h>, <string.h>, <time.h>, <wchar.h>nagłówków.

  • Przykład (z const)
#include <stdio.h>

int main()
{
    const size_t value = 200;
    size_t i;
    int arr[value];

    for (i = 0 ; i < value ; ++i)
    {
        arr[i] = i;
    }

    size_t size = sizeof(arr);
    printf("size = %zu\n", size);
}

Wynik -: size = 800


  • Przykład (bez const)
#include <stdio.h>

int main()
{
    size_t value = 200;
    size_t i;
    int arr[value];

    for (i = 0 ; i < value ; ++i)
    {
        arr[i] = i;
    }

    size_t size = sizeof(arr);
    printf("size = %zu\n", size);
}

Wynik -: size = 800

Kalana
źródło
-3

Z mojego zrozumienia size_twynika , że jest unsignedliczbą całkowitą, której rozmiar bitów jest wystarczająco duży, aby pomieścić wskaźnik architektury natywnej.

Więc:

sizeof(size_t) >= sizeof(void*)
David Zechiel
źródło
16
Nie prawda. Rozmiar wskaźnika może być większy niż size_t. Kilka przykładów: kompilatory C w trybie rzeczywistym x86 mogą mieć 32 bity FARlub HUGEwskaźniki, ale rozmiar_t wciąż wynosi 16 bitów. Kolejny przykład: Watcom C miał specjalny wskaźnik tłuszczu dla rozszerzonej pamięci o szerokości 48 bitów, ale size_tnie był. W przypadku kontrolera osadzonego z architekturą Harvard również nie ma korelacji, ponieważ obie dotyczą różnych przestrzeni adresowych.
Patrick Schlüter
1
A na tym stackoverflow.com/questions/1572099/... jest więcej przykładów AS / 400 ze 128-bitowymi wskaźnikami i 32-bitowymisize_t
Patrick Schlüter
Jest to rażąco nieprawdziwe. Trzymajmy to jednak tutaj
Antti Haapala