Interesujący punkt natknąłem się dzisiaj na recenzję Code Review . @ Veedrac zalecił w tej odpowiedzi, aby zmienne typy rozmiarów (np. int
I long
) zostały zastąpione stałymi typami rozmiarów, takimi jak uint64_t
i uint32_t
. Cytat z komentarzy do tej odpowiedzi:
Rozmiary int i long (a zatem wartości, które mogą przechowywać) są zależne od platformy. Z drugiej strony int32_t ma zawsze długość 32 bitów. Użycie int oznacza po prostu, że twój kod działa inaczej na różnych platformach, co na ogół nie jest tym, czego chcesz.
Powód, dla którego norma nie określa typowych typów, jest częściowo wyjaśniony tutaj przez @supercat. C został napisany jako przenośny w różnych architekturach, w przeciwieństwie do asemblera, który zwykle był używany do programowania systemów w tym czasie.
Myślę, że pierwotnie zamierzeniem projektu było, aby każdy typ inny niż int był najmniejszą rzeczą, która mogła poradzić sobie z liczbami o różnych rozmiarach, i że był to najbardziej praktyczny rozmiar „ogólnego zastosowania”, który mógłby obsłużyć +/- 32767.
Co do mnie, zawsze korzystałem int
i nie martwiłem się o alternatywy. Zawsze uważałem, że jest to najbardziej typ z najlepszą wydajnością, koniec historii. Jedynym miejscem, które moim zdaniem przydałaby się stała szerokość, byłoby przydatne do kodowania danych w celu przechowywania lub przesyłania przez sieć. Rzadko też widziałem typy o stałej szerokości w kodzie napisanym przez innych.
Czy utknąłem w latach 70., czy jest uzasadnienie dla zastosowania int
w erze C99 i później?
źródło
Odpowiedzi:
Istnieje powszechny i niebezpieczny mit, który pozwala
uint32_t
uchronić programistów przed martwieniem się wielkościąint
. Chociaż byłoby pomocne, gdyby Komitet Normalizacyjny określił sposób deklarowania liczb całkowitych za pomocą semantyki niezależnej od maszyny, typy niepodpisane, takie jakuint32_t
semantyka, są zbyt luźne, aby umożliwić pisanie kodu w sposób, który jest zarówno czysty, jak i przenośny; ponadto, podpisane typy, takie jakint32
semantyka, dla wielu aplikacji są niepotrzebnie ściśle zdefiniowane, a tym samym wykluczają przydatne optymalizacje.Rozważ na przykład:
Na maszynach gdzie
int
albo nie można przechowywać 4294967295, albo można przechowywać 18446744065119617025, pierwsza funkcja zostanie zdefiniowana dla wszystkich wartościn
iexponent
, a na jej zachowanie nie będzie miała wpływu wielkośćint
; Ponadto, średnia nie wymaga się, że dają to różne zachowanie na maszynach o dowolnym rozmiarzeint
Niektóre wartościn
iexponent
, jednakże powoduje to powołać niezdefiniowane Zachowanie w przypadku maszyn 4294967295 odwzorowane jakoint
ale +18446744065119617025 nie.Druga funkcja przyniesie nieokreślone zachowanie dla niektórych wartości
n
iexponent
na komputerach, na którychint
nie może przechowywać 4611686014132420609, ale zapewni określone zachowanie dla wszystkich wartości nan
iexponent
na wszystkich komputerach, na których jest to możliwe (specyfikacjeint32_t
sugerują, że zachowanie owijania uzupełnień dwóch na komputerach, na których jest to możliwe jest mniejszy niżint
).Historycznie, mimo że Standard nie mówił nic o tym, co kompilatory powinny zrobić
int
przepełnieniemupow
, kompilatory konsekwentnie dawałyby takie samo zachowanie, jakbyint
były wystarczająco duże, aby się nie przepełnić. Niestety, niektóre nowsze kompilatory mogą próbować „optymalizować” programy poprzez wyeliminowanie zachowań, które nie są wymagane przez standard.źródło
pow
, pamiętaj, że ten kod jest tylko przykładem i nie obsługujeexponent=0
!exponent=1
Spowoduje to, że n zostanie pomnożone samo, ponieważ dekrementacja jest wykonywana po sprawdzeniu, jeśli przyrost jest wykonywany przed sprawdzeniem ( tj. --exponent), żadne mnożenie nie zostanie wykonane, a samo n zostanie zwrócone.N^(2^exponent)
obliczana, ale obliczenia postaciN^(2^exponent)
są często używane do obliczania funkcji potęgowania, a potęgowanie mod-4294967296 jest przydatne do takich rzeczy, jak obliczanie skrótu konkatenacji dwóch ciągów znaków, których skróty są znane.uint32_t
przez 31 nigdy nie da UB, ale sprawny sposób na obliczenie 31 ^ N pociąga za sobą obliczenia 31 ^ (2 ^ N), które będąint32_t
czasami zdefiniowanie przepełnienia, a czasem nie, o czym zdajesz się wspominać, wydaje się mieć minimalne znaczenie w związku z faktem, że pozwala mi to przede wszystkim na zapobieganie przepełnieniu. A jeśli chcesz zdefiniowane przepełnienie, istnieje prawdopodobieństwo, że chcesz modulo wyniku jakiejś stałej wartości - więc i tak używasz typów o stałej szerokości.W przypadku wartości ściśle związanych ze wskaźnikami (a tym samym z ilością pamięci adresowalnej), takich jak rozmiary buforów, indeksy tablic i Windows '
lParam
, sensowne jest posiadanie typu liczby całkowitej o rozmiarze zależnym od architektury. Zatem typy o zmiennej wielkości są nadal przydatne. To dlatego mamy typedefssize_t
,ptrdiff_t
,intptr_t
, itd. Oni mają być typedefs ponieważ żaden z wbudowanego C całkowitą typów musi być wskaźnik wielkości.Więc pytanie brzmi, czy naprawdę
char
,short
,int
,long
, ilong long
są nadal użyteczne.IME, nadal jest powszechne w programach C i C ++ do
int
większości zastosowań. I przez większość czasu (tj. Gdy twoje liczby mieszczą się w zakresie ± 32 767 i nie masz rygorystycznych wymagań dotyczących wydajności), działa to dobrze.Ale co, jeśli chcesz pracować z liczbami w przedziale 17–32 bitów (np. Populacje dużych miast)? Możesz użyć
int
, ale byłoby to twarde zakodowanie zależności platformy. Jeśli chcesz ściśle przestrzegać standardu, możesz użyćlong
, co gwarantuje co najmniej 32 bity.Problem polega na tym, że standard C nie określa żadnego maksymalnego rozmiaru dla typu liczb całkowitych. Istnieją implementacje, w których
long
jest 64 bitów, co podwaja zużycie pamięci. A jeśli telong
będą to elementy tablicy z milionami przedmiotów, będziesz szaleć pamięć.Tak więc, ani
int
nielong
jest odpowiednim typem do użycia tutaj, jeśli chcesz, aby Twój program był zarówno wieloplatformowy, jak i efektywny pod względem pamięci. Enterint_least32_t
.long
, unikając problemów obcięcieint
int
, co pozwala uniknąć marnowania pamięci w wersji 64-bitowejlong
.int
OTOH, załóżmy, że nie potrzebujesz wielkich liczb lub wielkich tablic, ale potrzebujesz prędkości. I
int
może być wystarczająco duży na wszystkich platformach, ale niekoniecznie jest to najszybszy typ: systemy 64-bitowe zwykle nadal mają wersję 32-bitowąint
. Ale można użyćint_fast16_t
i uzyskać „najszybszy” typu, czy toint
,long
czylong long
.Istnieją więc praktyczne zastosowania dla typów z
<stdint.h>
. Standardowe typy liczb całkowitych nic nie znaczą . Zwłaszczalong
, który może mieć 32 lub 64 bity i może, ale nie musi być wystarczająco duży, aby pomieścić wskaźnik, w zależności od kaprysu twórców kompilatora.źródło
uint_least32_t
jest taki, że ich interakcje z innymi typami są jeszcze słabiej określone niż w przypadkuuint32_t
. IMHO, Standard powinien definiować typy takie jakuwrap32_t
izunum32_t
semantyką, którą każdy kompilator, który definiuje typuwrap32_t
, musi promować jako typ bez znaku w zasadniczo tych samych przypadkach, w których byłby promowany, gdybyint
miały 32 bity, a każdy kompilator, który definiuje typ,unum32_t
musi zapewnić, że podstawowe promocje arytmetyczne zawsze przekształcają go w typ podpisany, który jest w stanie utrzymać swoją wartość.intN_t
iuintN_t
, i których zdefiniowane zachowania byłyby spójne zintN_t
iuintN_t
, ale które dawałyby kompilatorom pewną swobodę w przypadku, gdyby kodowi przypisano wartości spoza ich zakresu [dopuszczając semantykę podobną do tych, które były być może przeznaczony douint_least32_t
, ale bez niejasności, takich jak to, czy dodanie auint_least16_t
i anint32_t
przyniosłoby podpisany lub usnted wynik.