Mam bardzo proste pytanie, które mnie zaskakuje przez długi czas. Mam do czynienia z sieciami i bazami danych, więc wiele danych, którymi się zajmuję, to liczniki 32-bitowe i 64-bitowe (niepodpisane), 32-bitowe i 64-bitowe identyfikatory (również nie mają znaczącego odwzorowania znaku). Praktycznie nigdy nie mam do czynienia z prawdziwymi słowami, które można wyrazić jako liczby ujemne.
Ja i moi współpracownicy rutynowo używamy niepodpisanych typów, takich jak uint32_t
i uint64_t
do tych spraw, a ponieważ tak się często zdarza, używamy ich również do indeksów tablic i innych typowych liczb całkowitych.
Jednocześnie różne przewodniki kodowania, które czytam (np. Google), zniechęcają do używania niepodpisanych typów liczb całkowitych i, o ile wiem, ani Java, ani Scala nie mają typów liczb całkowitych bez znaku.
Nie mogłem więc zorientować się, co należy zrobić: używanie podpisanych wartości w naszym środowisku byłoby bardzo niewygodne, a jednocześnie kodowanie przewodników, aby nalegać na zrobienie tego dokładnie.
źródło
Odpowiedzi:
Są na to dwie szkoły myślenia i żadna z nich się nigdy nie zgodzi.
Pierwszy argumentuje, że istnieją pewne koncepcje, które z natury są niepodpisane - takie jak indeksy tablic. Nie ma sensu używać podpisanych liczb dla tych, ponieważ może to prowadzić do błędów. Może także nakładać niepotrzebne ograniczenia na rzeczy - tablica, która używa 32-bitowych indeksów ze znakiem, może uzyskać dostęp tylko do 2 miliardów pozycji, a przejście na 32-bitowe liczby bez znaku pozwala na 4 miliardy pozycji.
Drugi argumentuje, że w każdym programie, który używa liczb niepodpisanych, prędzej czy później skończysz mieszaną arytmetyką bez podpisu. Może to dawać dziwne i nieoczekiwane wyniki: rzutowanie dużej wartości bez znaku na podpis daje liczbę ujemną, a odwrotnie rzutowanie liczby ujemnej na bez znaku daje dużą wartość dodatnią. To może być dużym źródłem błędów.
źródło
int
jest krótszy doint
wystarczy dla indeksów tablicowych 99,99% razy. Podpisane - niepodpisane problemy arytmetyczne są znacznie częstsze, dlatego mają pierwszeństwo w zakresie tego, czego należy unikać. Tak, kompilatory ostrzegają o tym, ale ile ostrzeżeń pojawia się podczas kompilacji dużego projektu? Ignorowanie ostrzeżeń jest niebezpieczne i złe, ale w prawdziwym świecie ...size_t
chyba, że jest to przypadek specjalny nie bez powodu.Po pierwsze, wytyczne kodowania Google C ++ nie są zbyt dobre do naśladowania: unika takich rzeczy jak wyjątki, ulepszenia itp., Które są podstawowymi elementami współczesnego C ++. Po drugie, tylko dlatego, że pewne wytyczne działają dla firmy X, nie oznacza, że będą dla ciebie odpowiednie. Nadal używałbym typów niepodpisanych, ponieważ bardzo ich potrzebujesz.
Przyzwoitą zasadą dla C ++ jest: wolę,
int
chyba że masz dobry powód, aby użyć czegoś innego.źródło
return false
jeśli niezmiennik ten nie zostanie ustalony. Możesz więc albo rozdzielić rzeczy i użyć funkcji init dla swoich obiektów, albo możesz rzucićstd::runtime_error
, pozwolić, aby nastąpiło odwinięcie stosu, i pozwolić wszystkim twoim obiektom RAII na automatyczne czyszczenie się, a Ty programista może obsłużyć wyjątek, w którym jest to wygodne dla aby to zrobić.nullptr
? zwrócić obiekt „domyślny” (cokolwiek to może znaczyć)? Niczego nie rozwiązałeś - ukryłeś problem pod dywanem i mam nadzieję, że nikt się nie dowie.signal(6)
? Jeśli użyjesz wyjątku, 50% programistów, którzy wiedzą, jak sobie z nimi poradzić, może napisać dobry kod, a resztę mogą ponieść ich rówieśnicy.W innych odpowiedziach brakuje przykładów ze świata rzeczywistego, więc dodam jeden. Jednym z powodów, dla których (osobiście) staram się unikać typów niepodpisanych.
Rozważ użycie standardowego rozmiaru_t jako indeksu tablicy:
Ok, zupełnie normalne. Następnie zastanów się, czy z jakiegoś powodu postanowiliśmy zmienić kierunek pętli:
A teraz to nie działa. Gdybyśmy zastosowali
int
jako iterator, nie byłoby problemu. Widziałem taki błąd dwa razy w ciągu ostatnich dwóch lat. Kiedyś stało się to w produkcji i było trudne do debugowania.Innym powodem dla mnie są irytujące ostrzeżenia, które powodują, że za każdym razem piszesz coś takiego :
To są drobne rzeczy, ale się sumują. Wydaje mi się, że kod jest czystszy, jeśli wszędzie używane są tylko liczby całkowite ze znakiem.
Edycja: Oczywiście, przykłady wyglądają głupio, ale widziałem ludzi popełniających ten błąd. Jeśli istnieje taki łatwy sposób, aby tego uniknąć, dlaczego go nie użyć?
Kiedy kompiluję następujący fragment kodu z VS2015 lub GCC, nie widzę ostrzeżeń z domyślnymi ustawieniami ostrzeżeń (nawet z -Wall dla GCC). Musisz poprosić o -Wextra, aby otrzymać ostrzeżenie o tym w GCC. Jest to jeden z powodów, dla których powinieneś zawsze kompilować z Wall i Wextra (i używać analizatora statycznego), ale w wielu prawdziwych projektach ludzie tego nie robią.
źródło
for (size_t i = n - 1; i < n; --i)
aby wszystko działało poprawnie.size_t
odwrotnością, istnieje wytyczna kodowania w stylufor (size_t revind = 0u; revind < n; ++revind) { size_t ind = n - 1u - revind; func(ind); }
int
? :)int
jest wystarczająco duży, aby pomieścić wszystkie prawidłowe wartościsize_t
. W szczególnościint
może zezwalać na liczby tylko do 2 ^ 15-1 i zwykle robi to w systemach, które mają limity alokacji pamięci 2 ^ 16 (lub w niektórych przypadkach nawet wyższe).long
może być bezpieczniejszym zakładem, choć nadal nie gwarantuje, że zadziała. Tylkosize_t
gwarantuje się, że działa na wszystkich platformach i we wszystkich przypadkach.Problem polega na tym, że napisałeś pętlę w nieuczciwy sposób, co prowadzi do błędnego zachowania. Konstrukcja pętli przypomina nauczenie początkujących dla typów podpisanych (co jest poprawne i poprawne), ale po prostu nie pasuje do niepodpisanych wartości. Ale nie może to służyć jako argument przeciwny używaniu typów niepodpisanych, zadaniem tutaj jest po prostu poprawne wykonanie pętli. Można to łatwo naprawić, aby niezawodnie działało dla typów niepodpisanych, takich jak:
Ta zmiana po prostu odwraca sekwencję porównywania i operacji dekrementacji i jest moim zdaniem najbardziej skutecznym, niezakłócającym, czystym i najkrótszym sposobem na obsługę niepodpisanych liczników w pętlach wstecznych. Zrobiłbyś to samo (intuicyjnie), używając pętli while:
Niedomiar nie może wystąpić, przypadek pustego pojemnika jest domyślnie objęty, tak jak w dobrze znanym wariancie dla podpisanej pętli licznika, a korpus pętli może pozostać niezmieniony w porównaniu do podpisanego licznika lub pętli przekazywania. Musisz tylko przyzwyczaić się do nieco dziwnie wyglądającej konstrukcji pętli. Ale po tym, jak zobaczyłeś to kilkanaście razy, nie ma już nic niezrozumiałego.
Miałbym szczęście, gdyby kursy dla początkujących pokazywały nie tylko prawidłową pętlę dla typów podpisanych, ale także dla typów niepodpisanych. Pozwoliłoby to uniknąć kilku błędów, które należy winić IMHO nieświadomym deweloperom zamiast obwiniać niepodpisany typ.
HTH
źródło
Istnieją niepodpisane liczby całkowite z jakiegoś powodu.
Rozważmy na przykład przekazywanie danych jako pojedynczych bajtów, np. W pakiecie sieciowym lub buforze plików. Czasami możesz spotkać takie bestie, jak 24-bitowe liczby całkowite. Łatwo przesunięte bitowo z trzech 8-bitowych liczb całkowitych bez znaku, nie tak łatwo z 8-bitowymi liczbami całkowitymi ze znakiem.
Lub pomyśl o algorytmach wykorzystujących tabele odnośników. Jeśli znak jest 8-bitową liczbą całkowitą bez znaku, możesz indeksować tabelę wyszukiwania według wartości znaku. Co jednak robisz, jeśli język programowania nie obsługuje liczb całkowitych bez znaku? Miałbyś ujemne indeksy do tablicy. Cóż, myślę, że możesz użyć czegoś takiego,
charval + 128
ale to po prostu brzydkie.W rzeczywistości wiele formatów plików używa liczb całkowitych bez znaku, a jeśli język programowania aplikacji nie obsługuje liczb całkowitych bez znaku, może to stanowić problem.
Następnie rozważ numery sekwencyjne TCP. Jeśli napiszesz kod przetwarzania TCP, na pewno będziesz chciał użyć liczb całkowitych bez znaku.
Czasami wydajność ma tak duże znaczenie, że naprawdę potrzebujesz dodatkowej liczby liczb całkowitych bez znaku. Rozważmy na przykład urządzenia IoT dostarczane w milionach. Można wówczas uzasadnić wiele zasobów programistycznych, które można przeznaczyć na mikrooptymalizacje.
Argumentowałbym, że uzasadnienie unikania używania liczb całkowitych bez znaku (arytmetyka znaków mieszanych, porównania znaków mieszanych) można pokonać kompilatorem z odpowiednimi ostrzeżeniami. Takie ostrzeżenia zwykle nie są domyślnie włączone, ale patrz np.
-Wextra
Lub osobno-Wsign-compare
(automatyczne włączanie w C przez-Wextra
, chociaż nie sądzę, że jest włączane automatycznie w C ++) i-Wsign-conversion
.Niemniej jednak w razie wątpliwości użyj podpisanego typu. Wiele razy jest to dobry wybór. I włącz te ostrzeżenia kompilatora!
źródło
Istnieje wiele przypadków, w których liczby całkowite nie reprezentują liczb, ale na przykład maska bitowa, identyfikator itp. Zasadniczo przypadki, w których dodanie 1 do liczby całkowitej nie ma żadnego znaczącego wyniku. W takich przypadkach użyj niepodpisanego.
Istnieje wiele przypadków, w których wykonujesz arytmetykę liczbami całkowitymi. W takich przypadkach należy użyć liczb całkowitych ze znakiem, aby uniknąć niewłaściwego zachowania w pobliżu zera. Zobacz wiele przykładów z pętlami, w których uruchomienie pętli do zera albo używa bardzo nieintuicyjnego kodu, albo jest zepsute z powodu użycia niepodpisanych liczb. Istnieje argument „ale wskaźniki nigdy nie są ujemne” - jasne, ale na przykład różnice wskaźników są ujemne.
W bardzo rzadkim przypadku, gdy indeksy przekraczają 2 ^ 31, ale nie 2 ^ 32, nie używasz liczb całkowitych bez znaku, używasz liczb całkowitych 64-bitowych.
Wreszcie ładna pułapka: w pętli „dla (i = 0; i <n; ++ i) a [i] ...” jeśli i jest niepodpisany 32-bitowy, a pamięć przekracza 32-bitowe adresy, kompilator nie może zoptymalizować dostęp do [i] poprzez zwiększenie wskaźnika, ponieważ przy i = 2 ^ 32 - 1 zawijam się. Nawet gdy n nigdy nie jest tak duży. Użycie podpisanych liczb całkowitych pozwala tego uniknąć.
źródło
Wreszcie znalazłem naprawdę dobrą odpowiedź: „Bezpieczne programowanie książki kucharskiej” J.Viega i M.Messier ( http://shop.oreilly.com/product/9780596003944.do )
Problemy bezpieczeństwa z podpisanymi liczbami całkowitymi:
Występują problemy z podpisanymi konwersjami <-> niepodpisanymi, więc nie zaleca się używania miksu.
źródło