W C ++ size_t
(lub, bardziej poprawnie, T::size_type
który jest „zwykle” size_t
; tj. unsigned
Typ) jest używany jako wartość zwracana dla size()
argumentu argumentu operator[]
itd. (Patrz std::vector
, i in.)
Z drugiej strony języki .NET używają int
(i opcjonalnie long
) do tego samego celu; w rzeczywistości języki zgodne z CLS nie są wymagane do obsługi typów niepodpisanych .
Biorąc pod uwagę, że .NET jest nowszy niż C ++, coś mi mówi, że mogą występować problemy z używaniem unsigned int
nawet do rzeczy, które „nie mogą” być ujemne, takie jak indeks tablicy lub długość. Czy podejście C ++ jest „historycznym artefaktem” dla kompatybilności wstecznej? Czy są prawdziwe i znaczące kompromisy między tymi dwoma podejściami?
Dlaczego to ma znaczenie? Cóż ... czego powinienem użyć dla nowej wielowymiarowej klasy w C ++; size_t
czy int
?
struct Foo final // e.g., image, matrix, etc.
{
typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
typedef size_t size_type; // c.f., std::vector<>
dimension_type bar_; // maybe rows, or x
dimension_type baz_; // e.g., columns, or y
size_type size() const { ... } // STL-like interface
};
-1
zwracane są funkcje zwracające indeks wskazujący „nie znaleziono” lub „poza zakresem”. Jest również zwracany zCompare()
funkcji (implementującychIComparable
). 32-bitowa liczba int jest uważana za typ dla liczby ogólnej, z tego, co mam nadzieję, są oczywiste powody.Odpowiedzi:
Tak. W przypadku niektórych rodzajów aplikacji, takich jak przetwarzanie obrazu lub przetwarzanie tablic, często konieczne jest uzyskanie dostępu do elementów w stosunku do bieżącej pozycji:
W tego typu aplikacjach nie można przeprowadzić sprawdzania zasięgu przy liczbach całkowitych bez znaku, bez dokładnego przemyślenia:
Zamiast tego musisz zmienić ekspresję sprawdzania zasięgu. To jest główna różnica. Programiści muszą również pamiętać reguły konwersji liczb całkowitych. W razie wątpliwości ponownie przeczytaj http://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions
Wiele aplikacji nie musi używać bardzo dużych indeksów tablicowych, ale musi przeprowadzać kontrolę zasięgu. Co więcej, wielu programistów nie jest przeszkolonych do wykonywania gimnastyki polegającej na przestawianiu wyrażeń. Jedna utracona szansa otwiera drzwi do exploita.
C # jest rzeczywiście zaprojektowany dla aplikacji, które nie będą wymagały więcej niż 2 ^ 31 elementów na tablicę. Na przykład aplikacja arkusza kalkulacyjnego nie musi zajmować się tyloma wierszami, kolumnami lub komórkami. C # radzi sobie z górnym limitem, mając opcjonalną sprawdzoną arytmetykę, którą można włączyć dla bloku kodu ze słowem kluczowym bez bałaganu z opcjami kompilatora. Z tego powodu C # preferuje użycie podpisanej liczby całkowitej. Kiedy te decyzje są rozpatrywane w całości, ma to sens.
C ++ jest po prostu inny i trudniej jest uzyskać poprawny kod.
Jeśli chodzi o praktyczne znaczenie umożliwienia podpisanej arytmetyki usunięcia potencjalnego naruszenia „zasady najmniejszego zdziwienia”, przykładem może być OpenCV, który używa 32-bitowej liczby całkowitej ze znakiem dla wskaźnika elementu macierzy, rozmiaru tablicy, liczby kanałów pikseli itp. Obraz przetwarzanie jest przykładem domeny programowania, która mocno wykorzystuje względny indeks tablicowy. Niedopełniony niedopełnienie liczby całkowitej (zawinięty wynik ujemny) poważnie skomplikuje implementację algorytmu.
źródło
Ta odpowiedź naprawdę zależy od tego, kto będzie używał twojego kodu i jakie standardy chcą zobaczyć.
size_t
jest liczbą całkowitą mającą cel:Dlatego za każdym razem, gdy chcesz pracować z rozmiarem obiektów w bajtach, powinieneś użyć
size_t
. Teraz w wielu przypadkach nie używasz tych wymiarów / indeksów do zliczania bajtów, ale większość programistów decyduje się na użyciesize_t
ich dla zachowania spójności.Pamiętaj, że zawsze powinieneś używać,
size_t
jeśli twoja klasa ma wygląd i styl klasy STL. Wszystkie klasy STL w specyfikacji używająsize_t
. Jest to ważne dla kompilatora do typedefsize_t
byćunsigned int
, i to jest również ważne, aby była ona typedefed dounsigned long
. Jeśli użyjeszint
lublong
bezpośrednio, ostatecznie spotkasz się z kompilatorami, w których osoba, która myśli, że twoja klasa postępowała zgodnie ze stylem STL, zostaje uwięziona, ponieważ nie postępujesz zgodnie ze standardem.Jeśli chodzi o używanie podpisanych typów, jest kilka zalet:
int
, ale znacznie trudniej jest zaśmiecać kodunsigned int
.int32_t
iuint32_t
). To może uprościć interoperacyjność APIDuża wada podpisanych typów jest oczywista: tracisz połowę swojej domeny. Podpisany numer nie może być liczony tak wysoko jak numer bez znaku. Kiedy pojawiło się C / C ++, było to bardzo ważne. Trzeba było mieć możliwość pełnego wykorzystania możliwości procesora, a do tego trzeba było używać liczb bez znaku.
W przypadku rodzajów aplikacji, na które ukierunkowane jest .NET, nie było tak silnej potrzeby indeksowania niepodpisanego pełnej domeny. Wiele celów takich liczb jest po prostu nieważnych w zarządzanym języku (przychodzi na myśl pula pamięci). Wraz z pojawieniem się platformy .NET 64-bitowe komputery były wyraźnie przyszłością. Jesteśmy daleko od potrzeby pełnego zakresu 64-bitowej liczby całkowitej, więc poświęcenie jednego bitu nie jest tak bolesne jak wcześniej. Jeśli naprawdę potrzebujesz 4 miliardów indeksów, po prostu przełącz się na używanie 64-bitowych liczb całkowitych. W najgorszym przypadku uruchamiasz go na 32-bitowej maszynie i jest on trochę powolny.
Uważam tę wymianę za wygodę. Jeśli akurat masz wystarczającą moc obliczeniową, że nie masz nic przeciwko marnowaniu części swojego indeksu, którego nigdy nie będziesz nigdy używać, to wygodnie jest po prostu wpisać
int
lublong
odejść od niego. Jeśli okaże się, że naprawdę tego chciałeś, prawdopodobnie powinieneś zwrócić uwagę na podpis swoich numerów.źródło
size()
byłoreturn bar_ * baz_;
; czy to nie stwarza teraz potencjalnego problemu z przepełnieniem liczb całkowitych (zawijaniem), którego nie miałbym, gdybym nie używałsize_t
?bar_ * baz_
może przepełnić liczbę całkowitą ze znakiem, ale nie liczbę całkowitą bez znaku. Ograniczając się do C ++, warto zauważyć, że w specyfikacji zdefiniowano przepełnienie niepodpisane, ale przepełnienie ze znakiem jest nieokreślonym zachowaniem, więc jeśli pożądana jest arytmetyka modulo niepodpisanych liczb całkowitych, zdecydowanie je wykorzystaj, ponieważ jest faktycznie zdefiniowane!size()
przepełniła podpisaną mnożenie, jesteś w język UB ziemi. (i wfwrapv
trybie, patrz dalej :) Gdy wtedy , przy odrobinie odrobiny więcej, przepełniło się niepodpisane mnożenie, ty w krainie błędów użytkownika - zwrócisz fałszywy rozmiar. Więc nie sądzę, żeby niepodpisany kupował tutaj dużo.Myślę, że powyższa odpowiedź rwonga doskonale podkreśla problemy.
Dodam mój 002:
size_t
, czyli rozmiar, który ...... jest wymagany tylko w przypadku indeksów zakresów
sizeof(type)==1
, gdy mamy do czynienia zchar
typami byte ( ). (Ale zauważamy, że może być mniejszy niż typ ptr :xxx::size_type
może być stosowany w 99,9% przypadków, nawet jeśli byłby to rozmiar wielkości ze znakiem. (porównajssize_t
)std::vector
i przyjaciele wybralisize_t
, bez znaku , rozmiar i indeksowanie, jest uważany przez niektórych za wadę projektową. Zgadzam się. (Poważnie, poświęć 5 minut i obejrzyj błyskawiczną rozmowę CppCon 2016: Jon Kalb „unsigned: A Guideline for Better Code” .)size_t
aby zachować spójność ze Standardową Biblioteką, lub użyj ( podpisanego )intptr_t
lubssize_t
do łatwych i mniej podatnych na błędy obliczeń indeksowania.intptr_t
jeśli chcesz podpisać się i chcesz rozmiar słowa maszynowego lub użyjssize_t
.Aby bezpośrednio odpowiedzieć na pytanie, nie jest to całkowicie „artefakt historyczny”, ponieważ teoretyczny problem konieczności zajęcia się więcej niż połową („indeksowania” lub) przestrzeni adresowej musi być, aehm, w jakiś sposób rozwiązany w języku niskiego poziomu, takim jak C ++.
Z perspektywy czasu ja osobiście tak myślę jest to błąd projektowy, który Biblioteka Standardowa stosuje bez znaku w
size_t
całym miejscu, nawet tam, gdzie nie reprezentuje surowego rozmiaru pamięci, ale pojemność wpisywanych danych, jak w przypadku kolekcji:Będę powtarzać porady Jona tutaj:
(* 1) tj. Unsigned == maska bitowa, nigdy nie wykonuj na nim obliczeń matematycznych (tutaj pojawia się pierwszy wyjątek - możesz potrzebować licznika, który się otacza - to musi być typ bez znaku)
(* 2) ilości oznaczające coś, co się liczy i / lub robi matematykę.
źródło
ssize_t
, zdefiniowany jako podpisany wisioreksize_t
zamiastintptr_t
, który może przechowywać dowolny wskaźnik (niebędący członkiem), a zatem może być większy?size_t
nieco pomieszać definicję. Zobacz size_t vs. intptr i en.cppreference.com/w/cpp/types/size_t Nauczyłem się dzisiaj czegoś nowego. :-) Myślę, że reszta argumentów stoi, zobaczę, czy mogę naprawić użyte typy.Dodam tylko, że ze względu na wydajność zwykle używam size_t, aby upewnić się, że błędne obliczenia powodują niedopełnienie, co oznacza, że obie kontrole zakresu (poniżej zera i powyżej size ()) można zmniejszyć do jednego:
przy użyciu podpisanego int:
przy użyciu unsigned int:
źródło