Dlaczego rozmiar t jest niepodpisany?

Bjarne Stroustrup napisał w języku programowania C++:

Typy unsigned integer są idealne do zastosowań, które traktują pamięć jako tablica bitów. Użycie unsigned zamiast int, aby uzyskać jeszcze jeden bit do reprezentowanie dodatnich liczb całkowitych prawie nigdy nie jest dobrym pomysłem. Próby upewnij się, że niektóre wartości są dodatnie, deklarując zmienne niepodpisane zazwyczaj zostaną pokonane przez Ukryte reguły konwersji.

Size_t wydaje się być niepodpisane " aby zyskać jeszcze jeden bit do reprezentowania dodatnich liczb całkowitych". Czy więc był to błąd (lub kompromis), a jeśli tak, to czy powinniśmy zminimalizować użycie go w naszym własnym kodzie?

Kolejny istotny artykuł Scotta Meyersa jest tutaj . Podsumowując, zaleca, aby nie używać unsigned in interfaces, niezależnie od tego, czy wartość jest zawsze dodatnia, czy nie. Innymi słowy, nawet jeśli wartości ujemne nie mają sensu, niekoniecznie należy używać unsigned.

Author: manlio, 2012-04-16

4 answers

size_t jest niepodpisany ze względów historycznych.

W architekturze ze wskaźnikami 16-bitowymi, jak np. "mały" model programowania DOS, ograniczenie ciągów do 32 KB byłoby niepraktyczne.

Z tego powodu standard C wymaga (poprzez wymagane zakresy) ptrdiff_t, podpisanego odpowiednika size_t i wynikowego typu różnicy kursora, aby mieć efektywnie 17 bitów.

Te powody mogą nadal mieć zastosowanie w niektórych częściach świata programowania wbudowanego.

Jednak nie stosuje się do nowoczesnego 32-bitowego lub 64-bitowego programowania, gdzie znacznie ważniejszą kwestią jest to, że niefortunne reguły domyślnej konwersji w C i C++ sprawiają, że niepodpisane typy są atraktorami błędów, gdy są używane do liczb (a więc operacji arytmetycznych i porównań wielkości). Z 20-20 perspektywy czasu możemy teraz zobaczyć, że decyzja o przyjęciu tych szczególnych zasad konwersji, gdzie np. {3]} jest praktycznie gwarantowana, była raczej głupia i niepraktyczna. Decyzja ta oznacza jednak, że we współczesnym programowaniu przyjmowanie typów niepodpisanych dla liczb ma poważne wady i nie ma żadnych zalet – z wyjątkiem zaspokojenia uczuć tych, którzy uważają unsigned za samoopisową nazwę typu i nie myślą o typedef int MyType.

Podsumowując, to nie był błąd. Była to decyzja z bardzo wówczas racjonalnych, praktycznych powodów programowych. Nie miało to nic wspólnego z przeniesieniem oczekiwań z języków sprawdzonych jak Pascal do C++ (co jest błędem, ale bardzo powszechnym, nawet jeśli niektórzy z tych, którzy to robią, Nigdy nie słyszeli o Pascalu).
 58
Author: Cheers and hth. - Alf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-16 04:00:20

size_t jest unsigned ponieważ rozmiary ujemne nie mają sensu.

(Z komentarzy:)

To nie tyle zapewnienie, co stwierdzenie, co jest. Kiedy ostatnio widziałeś listę rozmiaru -1? Podążaj za tą logiką zbyt daleko i przekonasz się, że niepodpisane nie powinno w ogóle istnieć, a operacje bitowe również nie powinny być dozwolone. - geekozaur

Bardziej do rzeczy: adresy, z powodów, o których powinieneś pomyśleć, nie są podpisane. Rozmiary generowane są przez porównywanie adresów; traktowanie adres jako podpisany zrobi bardzo złą rzecz, a użycie podpisanej wartości dla wyniku spowoduje utratę danych w sposób, który odczyt cytatu Stroustrup najwyraźniej uważa za akceptowalny, ale w rzeczywistości nie jest. Być może możesz wyjaśnić, co powinien zrobić adres negatywny. - geekozaur

 26
Author: geekosaur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:25:20

Powodem tworzenia typów indeksów niepodpisanych jest symetria z preferencjami C i c++Dla interwałów półotwartych. A jeśli typy indeksów mają być niepodpisane, wygodne jest również niepodpisanie typu rozmiaru.


W C, możesz mieć wskaźnik, który wskazuje na tablicę. Poprawny wskaźnik może wskazywać dowolny element tablicy lub jeden element za końcem tablicy. Nie może wskazywać na jeden element przed początkiem tablicy.

int a[2] = { 0, 1 };
int * p = a;  // OK
++p;  // OK, points to the second element
++p;  // Still OK, but you cannot dereference this one.
++p;  // Nope, now you've gone too far.
p = a;
--p;  // oops!  not allowed

C++ zgadza się i rozszerza ten pomysł na Iteratory.

Argumenty przeciwko niepodpisanym typom indeksów często przedstawiają przykład przechodzenia tablicy od tyłu do przodu, a kod często wygląda tak:

// WARNING:  Possibly dangerous code.
int a[size] = ...;
for (index_type i = size - 1; i >= 0; --i) { ... }

Ten kod działa tylko Jeśli index_type jest podpisany, co jest używane jako argument, że typy indeksów powinny być podpisane (i że, przez rozszerzenie, rozmiary powinny być podpisane).

Ten argument jest nieinwazyjny, ponieważ kod nie jest idiomatyczny. Obserwuj co się stanie jeśli spróbujemy przepisać tę pętlę ze wskaźnikami zamiast indeksów:

// WARNING:  Bad code.
int a[size] = ...;
for (int * p = a + size - 1; p >= a; --p) { ... }

Yikes, teraz mamy nieokreślone zachowanie! Ignorując problem, Gdy size wynosi 0, mamy problem na końcu iteracji, ponieważ generujemy nieprawidłowy wskaźnik, który wskazuje na element przed pierwszym. To nieokreślone zachowanie, nawet jeśli nigdy nie spróbujemy dereferencji tego wskaźnika.

Więc można argumentować, aby to naprawić, zmieniając standard języka, aby było legalne, aby mieć wskaźnik, który wskazuje na element przed pierwszym, ale to się raczej nie stanie. Interwał półotwarty jest podstawowym elementem składowym tych języków, więc zamiast tego napiszmy lepszy kod.

Poprawne rozwiązanie oparte na wskaźniku to:

int a[size] = ...;
for (int * p = a + size; p != a; ) {
  --p;
  ...
}

Wielu uważa to za niepokojące, ponieważ dekrement znajduje się teraz w ciele pętli, a nie w nagłówku, ale tak się dzieje, gdy składnia for jest zaprojektowana głównie dla pętli do przodu przez półotwarte interwały. (Iteratory odwrotne rozwiązują tę asymetrię odkładając dekrementacja.)

Teraz, przez analogię, rozwiązanie oparte na indeksie staje się:

int a[size] = ...;
for (index_type i = size; i != 0; ) {
  --i;
  ...
}

Działa to niezależnie od tego, czy index_type jest podpisane, czy niepodpisane, ale opcja niepodpisana daje kod, który mapuje bezpośrednio do idiomatycznych wersji wskaźnika i iteratora. Unsigned oznacza również, że podobnie jak w przypadku wskaźników i iteratorów, będziemy w stanie uzyskać dostęp do każdego elementu sekwencji-nie oddajemy połowy naszego możliwego zakresu, aby reprezentować nonsensowne wartości. Chociaż nie jest to praktyczny problem w 64-bitowy świat, może być bardzo realnym problemem w 16-bitowym wbudowanym procesorze lub w budowaniu abstrakcyjnego typu kontenera dla rzadkich danych w ogromnym zakresie, który może nadal dostarczać identyczne API jako natywny kontener.

 3
Author: Adrian McCarthy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-20 20:28:52

Z drugiej strony ...

Mit 1: std::size_t jest niepodpisany ze względu na istniejące ograniczenia, które już nie mają zastosowania.

Istnieją dwa "historyczne" powody, o których się tu powszechnie mówi:

  1. sizeof zwraca std::size_t, który został niepodpisany od czasów C.
  2. Procesory miały mniejsze rozmiary słów, więc ważne było, aby wycisnąć ten dodatkowy bit zakresu.

Ale żaden z tych powodów, mimo że jest bardzo stary, nie są w rzeczywistości zdegradowany do historii.

sizeof still zwraca std::size_t, która jest nadal niepodpisana. Jeśli chcesz współpracować z sizeof lub kontenerami biblioteki standardowej, musisz użyć std::size_t.

Alternatywy są gorsze: możesz wyłączyć ostrzeżenia o porównywaniu podpisanych / niepodpisanych oraz ostrzeżenia o konwersji rozmiaru i mieć nadzieję, że wartości będą zawsze w nakładających się zakresach, dzięki czemu możesz zignorować utajone błędy przy użyciu różnych typów, które potencjalnie wprowadzają para. Albo możesz wykonaj lot sprawdzania zakresu i jawnych konwersji. Możesz też wprowadzić własny typ rozmiaru za pomocą inteligentnych wbudowanych konwersji w celu scentralizowania sprawdzania zakresu, ale żadna inna biblioteka nie będzie używać twojego typu rozmiaru.

[14]}i podczas gdy większość popularnych obliczeń odbywa się na 32 - i 64-bitowych procesorach, C++ jest nadal używany na 16-bitowych mikroprocesorach w systemach wbudowanych, nawet dzisiaj. Na tych mikroprocesorach często bardzo przydatne jest posiadanie wartości o wielkości słowa, która może reprezentować dowolną wartość w Twojej pamięci.

Nasz nowy kod nadal musi współpracować ze standardową biblioteką. Jeśli nasz nowy kod używa typów podpisanych, podczas gdy biblioteka standardowa nadal używa niepodpisanych, utrudniamy każdemu konsumentowi, który musi używać obu tych typów.

Mit 2: nie potrzebujesz tego dodatkowego kawałka. (Aka, nigdy nie będziesz miał ciąg większy niż 2GB, gdy przestrzeń adresowa wynosi tylko 4GB.)

Rozmiary i indeksy nie służą tylko pamięci. Twoja przestrzeń adresowa może być ograniczone, ale możesz przetwarzać pliki o wiele większe niż przestrzeń adresowa. I chociaż możesz nie mieć ciąg z więcej 2GB, możesz wygodnie mieć bitset z więcej niż 2gbits. Nie zapomnij też o wirtualnych kontenerach zaprojektowanych z myślą o ograniczonych danych.

Mit 3: zawsze można użyć szerszego typu podpisanego.

Nie zawsze. To prawda, że dla zmiennej lokalnej lub dwóch, możesz użyć std::int64_t (zakładając, że Twój system ma jedną) lub signed long long i prawdopodobnie napisać całkowicie rozsądny kod. (Ale nadal będziesz potrzebował wyraźnych rzutów i dwa razy więcej sprawdzania granic, albo będziesz musiał wyłączyć ostrzeżenia kompilatora, które mogły zaalarmować Cię o błędach w innym miejscu kodu.)

Ale co jeśli budujesz dużą tabelę indeksów? Czy naprawdę chcesz dodatkowe dwa lub cztery bajty dla każdego indeksu, gdy potrzebujesz tylko jednego bit? Nawet jeśli masz dużo pamięci i nowoczesny procesor, dzięki czemu stół jest dwa razy większy może mieć szkodliwy wpływ na lokalizację odniesienia, a wszystkie Twoje kontrole zasięgu są teraz dwuetapowe, zmniejszając skuteczność przewidywania gałęzi. A co jeśli nie będziesz miał tyle pamięci?

Mit 4 : arytmetyka Niepodpisana jest zaskakująca i nienaturalna.

To oznacza, żepodpisane arytmetyka nie jest zaskakująca ani w jakiś sposób bardziej naturalna. I, być może jest to, gdy myśli w kategoriach matematyki, gdzie wszystkie podstawowe operacje arytmetyczne są zamknięty na zbiorze wszystkich liczb całkowitych.

Ale nasze komputery nie działają z liczbami całkowitymi. Pracują z ułamkiem infinitezymalnym liczb całkowitych. Nasza arytmetyka podpisana nie jest zamknięta na zbiorze wszystkich liczb całkowitych. Mamy przepełnienie i niedopełnienie. Dla wielu jest to tak zaskakujące i nienaturalne, że w większości po prostu to ignorują.

To jest bug:

auto mid = (min + max) / 2;  // BUGGY

Jeśli min i max są podpisane, suma może się przepełnić, co daje nieokreślone zachowanie. Większość z nas rutynowo tęskni za tym tego typu błędy, ponieważ zapominamy, że dodawanie nie jest zamykane nad zestawem podpisanych intów. Unikamy tego, ponieważ nasze Kompilatory zazwyczaj generują kod, który robi coś rozsądnego (ale wciąż zaskakującego).

Jeśli min i max są niepodpisane, suma może nadal przepełnić się, ale niezdefiniowane zachowanie zniknie. Nadal otrzymasz złą odpowiedź, więc nadal jest to zaskakujące, ale nie bardziej zaskakujące niż było to z podpisanymi ints.

Nadchodzi prawdziwa niezapisana niespodzianka z odejmowaniem: jeśli odejmujesz większą niepodpisaną liczbę całkowitą od mniejszej, skończysz z dużą liczbą. Ten wynik nie jest bardziej zaskakujący niż jeśli podzielisz przez 0.

Nawet jeśli możesz wyeliminować niepodpisane typy ze wszystkich interfejsów API, nadal musisz być przygotowany na te niepodpisane "niespodzianki", jeśli masz do czynienia ze standardowymi kontenerami, formatami plików lub protokołami przewodowymi. Czy naprawdę warto dodać tarcie do interfejsów API, aby "rozwiązać" tylko część problemu?

 1
Author: Adrian McCarthy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-25 14:34:37