Wydaje się, że uint32_t
jest to znacznie bardziej rozpowszechnione niż uint_fast32_t
(zdaję sobie sprawę, że to niepotwierdzone dowody). Wydaje mi się to jednak sprzeczne z intuicją.
Prawie zawsze, gdy widzę zastosowanie implementacji uint32_t
, wszystko, czego naprawdę chce, to liczba całkowita, która może przechowywać wartości do 4 294 967 295 (zwykle znacznie niższa granica między 65 535 a 4 294 967 295).
Wydaje się dziwne, aby następnie wykorzystać uint32_t
jako „dokładnie 32 bitów” gwarancja nie jest potrzebna, a „najszybszą dostępną> = 32 bitów” gwarancja uint_fast32_t
wydają się być dokładnie taki dobry pomysł. Co więcej, chociaż jest zwykle wdrażany, uint32_t
nie ma gwarancji, że będzie istniał.
Dlaczego więc miałoby to uint32_t
być preferowane? Czy jest po prostu lepiej znany, czy też są przewagi techniczne nad drugim?
uint32_fast_t
, która, jeśli dobrze rozumiem, ma co najmniej 32 bity (co oznacza, że może być ich więcej? Brzmi to myląco). Obecnie używamuint32_t
i przyjaciół w moim projekcie, ponieważ pakuję te dane i wysyłam je przez sieć i chcę, aby nadawca i odbiorca dokładnie wiedzieli, jak duże są pola. Wygląda na to, że to może nie być najsolidniejsze rozwiązanie, ponieważ platforma może nie być implementowanauint32_t
, ale najwyraźniej wszystkie moje tak robią, więc nie przeszkadza mi to, co robię.uint32_t
nie daje ci tego (i szkoda, że nie mauint32_t_be
iuint32_t_le
, co byłoby bardziej odpowiednie dla prawie każdego możliwego przypadku, w którymuint32_t
jest obecnie najlepsza opcja).Odpowiedzi:
uint32_t
ma prawie takie same właściwości na każdej platformie, która go obsługuje. 1uint_fast32_t
ma bardzo mało gwarancji, jak zachowuje się w różnych systemach w porównaniu.Jeśli przełączysz się na platformę, która
uint_fast32_t
ma inny rozmiar, cały używany koduint_fast32_t
musi zostać ponownie przetestowany i zweryfikowany. Wszystkie założenia dotyczące stabilności wyjdą na jaw. Cały system będzie działał inaczej.Podczas pisania kodu możesz nawet nie mieć dostępu do
uint_fast32_t
systemu o rozmiarze innym niż 32 bity.uint32_t
nie będzie działać inaczej (patrz przypis).Poprawność jest ważniejsza niż szybkość. Dlatego przedwczesna poprawność jest lepszym planem niż przedwczesna optymalizacja.
W przypadku, gdy pisałem kod dla systemów, które
uint_fast32_t
miały 64 lub więcej bitów, mogłem przetestować swój kod w obu przypadkach i użyć go. Poza potrzebą i szansą zrobienie tego jest złym planem.Wreszcie,
uint_fast32_t
gdy przechowujesz go przez dowolny czas lub liczbę instancji, może to być wolniejsze niżuint32
po prostu z powodu problemów z rozmiarem pamięci podręcznej i przepustowością pamięci. Dzisiejsze komputery są znacznie częściej związane z pamięcią niż z procesorem iuint_fast32_t
mogą być szybsze w izolacji, ale nie po uwzględnieniu obciążenia pamięci.1 Jak @chux zauważył w komentarzu, jeśli
unsigned
jest większe niżuint32_t
, arytmetykauint32_t
przechodzi przez zwykłe promocje w postaci liczb całkowitych, a jeśli nie, pozostaje takuint32_t
. Może to powodować błędy. Nic nigdy nie jest doskonałe.źródło
unsigned
jest szerszy niż,uint32_t
a następnieuint32_t
na jednej platformie przechodzi zwykłe promocje na liczby całkowite, a na innej nie. Jednak zuint32_t
tymi liczbami całkowitymi problem matematyczny jest znacznie zmniejszony.uint32_t
jest tam, gdzie dokładne szczegóły reprezentacji maszyny danego typu są ważne, podczas gdyuint_fast32_t
tam, gdzie najważniejsza jest prędkość obliczeniowa, (nie) podpis i minimalny zasięg są ważne, a szczegóły reprezentacji są nieistotne. Istnieje również miejsce,uint_least32_t
w którym najważniejsza jest (nie) sygnalizacja i minimalny zasięg, zwartość jest ważniejsza niż szybkość, a dokładna reprezentacja nie jest niezbędna.uint32_t
zamiast innych typów, jest to, że zwykle nie mają takiego sprzętu do testowania . (To samo dotyczy wint32_t
mniejszym stopniu, a nawetint
ishort
).unsigned short
==uint32_t
iint
==int48_t
. Jeśli obliczasz coś podobnego(uint32_t)0xFFFFFFFF * (uint32_t)0xFFFFFFFF
, operandy są promowane dosigned int
i wywołują przepełnienie ze znakiem całkowitym, co jest niezdefiniowanym zachowaniem. Zobacz to pytanie.Uwaga: błędnie nazwane
uint32_fast_t
powinno byćuint_fast32_t
.uint32_t
ma bardziej rygorystyczną specyfikacjęuint_fast32_t
i zapewnia bardziej spójną funkcjonalność.uint32_t
plusy:uint32_t
Cons:Np .: Platformy bez 8/16/32-bitowych liczb całkowitych (9/18/ 36- bit, inne ).
Np .: Platformy używające dopełnienia innego niż 2. stary 2200
uint_fast32_t
plusy:To zawsze pozwolić wszystkie platformy, nowe i stare, szybko użyć / podstawowe rodzaje.
uint_fast32_t
Cons:uint32_fast_t
. Wygląda na to, że wielu po prostu nie potrzebuje i nie używa tego typu. Nawet nie użyliśmy właściwej nazwy!uint_fast32_t
to tylko przybliżenie pierwszego rzędu.Ostatecznie to, co najlepsze, zależy od celu kodowania. Jeśli nie ma kodowania dla bardzo szerokiej przenośności lub jakiejś niszowej funkcji wydajności, użyj
uint32_t
.Podczas korzystania z tych typów pojawia się inny problem: ich ranga w porównaniu z
int/unsigned
Przypuszczalnie
uint_fastN_t
mogłaby to być rangaunsigned
. Nie jest to określone, ale jest to pewien i sprawdzalny stan.W związku z tym
uintN_t
jest bardziej prawdopodobne niżuint_fastN_t
węższe rozszerzenieunsigned
. Oznacza to, że kod korzystający zuintN_t
matematyki jest bardziej narażony na promowanie liczb całkowitych niż wuint_fastN_t
przypadku przenośności.W tym względzie: przewaga przenośności
uint_fastN_t
przy wybranych operacjach matematycznych.Uwaga dodatkowa
int32_t
raczej niżint_fast32_t
: Na rzadkich komputerachINT_FAST32_MIN
może wynosić -2 147 483 647, a nie -2 147 483 648. Większy punkt:(u)intN_t
typy są ściśle określone i prowadzą do przenośnego kodu.źródło
uint32_fast_t
jest typem 64-bitowym, więc zapisuje sporadyczne rozszerzenie znaku i pozwalaimul rax, [mem]
zamiast oddzielnej instrukcji ładowania rozszerzającej zero, gdy jest używana z 64-bitowymi liczbami całkowitymi lub wskaźnikami. Ale to wszystko, co dostajesz za cenę podwójnego rozmiaru pamięci podręcznej i dodatkowego rozmiaru kodu (REX z prefiksem na wszystkim).popcnt
. I pamiętaj, że używanie tego typu jest bezpieczne tylko dla wartości 32-bitowych, ponieważ jest mniejszy na innych architekturach, więc płacisz ten koszt za nic.uint32_fast_t
na x86 jest okropnym wyborem. Operacji, które są szybsze, jest niewiele, a korzyści, gdy się pojawiają, są przeważnie niewielkie: różnice wimul rax, [mem]
przypadku, o którym wspomina @PeterCordes, są bardzo , bardzo małe: pojedynczy uop w domenie połączonej i zero w domenie nieużywanej. W najciekawszych scenariuszach nie doda nawet jednego cyklu. Zrównoważyć to w stosunku do dwukrotnego wykorzystania pamięci i gorszej wektoryzacji, ciężko jest zobaczyć, jak często wygrywa.fast_t
jeszcze gorzejint
: nie tylko ma różne rozmiary na różnych platformach, ale miałby różne rozmiary w zależności od decyzji optymalizacyjnych i różne rozmiary w różnych plikach! Z praktycznego punktu widzenia myślę, że nie działa nawet z optymalizacją całego programu: rozmiary w C i C ++ są ustalone, więcsizeof(uint32_fast_t)
lub cokolwiek, co decyduje o tym, nawet bezpośrednio, musi zawsze zwracać tę samą wartość, więc kompilatorowi byłoby bardzo trudno dokonać takiej przemiany.Głupia odpowiedź:
uint32_fast_t
, prawidłowa pisownia touint_fast32_t
.Praktyczna odpowiedź:
uint32_t
lubint32_t
dla swojej precyzyjnej semantyki, dokładnie 32 bitów z bez znaku zawijania arytmetycznego (uint32_t
) lub reprezentacji dopełnienia do 2 (int32_t
). Texxx_fast32_t
typy mogą być większe, a więc nieodpowiednie do przechowywania plików binarnych, zapakowanych w użyciu tablic i struktur, lub wysłać przez sieć. Co więcej, mogą nawet nie być szybsze.Pragmatyczna odpowiedź:
uint_fast32_t
, co pokazują komentarze i odpowiedzi, i prawdopodobnie zakłada po prostu,unsigned int
że ma tę samą semantykę, chociaż wiele obecnych architektur nadal ma 16-bitowe,int
a niektóre rzadkie próbki muzealne mają inne dziwne rozmiary int mniejsze niż 32.Odpowiedź UX:
uint32_t
,uint_fast32_t
jest wolniejszy w użyciu: wpisywanie zajmuje więcej czasu, szczególnie biorąc pod uwagę sprawdzanie pisowni i semantyki w dokumentacji C ;-)Elegancja ma znaczenie (oczywiście oparta na opinii):
uint32_t
wygląda na tyle źle, że wielu programistów woli zdefiniować własnyu32
lubuint32
typ ... Z tej perspektywyuint_fast32_t
wygląda niezdarnie i nie da się go naprawić. Nic dziwnego, że siedzi na ławce z przyjaciółmiuint_least32_t
i tak dalej.źródło
std::reference_wrapper
przypuszczam, ale czasami zastanawiam się, czy komisja standaryzacyjna naprawdę chce, aby używane były typy, które standaryzuje ...Jednym z powodów jest to, że
unsigned int
jest już „najszybszy” bez potrzeby stosowania specjalnych czcionek typu lub potrzeby dołączania czegoś. Więc jeśli potrzebujesz tego szybko, po prostu użyj podstawowegoint
lubunsigned int
typu.Chociaż standard nie gwarantuje wyraźnie, że jest najszybszy, robi to pośrednio , stwierdzając, że „Zwykłe wartości int mają naturalny rozmiar sugerowany przez architekturę środowiska wykonawczego” w 3.9.1. Innymi słowy,
int
(lub jego niepodpisany odpowiednik) jest tym, z czym procesor jest najbardziej wygodny.Teraz oczywiście nie wiesz, jaki
unsigned int
może być rozmiar . Wiesz tylko, że jest co najmniej tak duży, jakshort
(i pamiętam, żeshort
musi mieć co najmniej 16 bitów, chociaż nie mogę teraz znaleźć tego w standardzie!). Zwykle jest to po prostu po prostu 4 bajty, ale teoretycznie może być większy, a w skrajnych przypadkach nawet mniejszy (chociaż osobiście nigdy nie spotkałem się z architekturą, w której tak było, nawet na komputerach 8-bitowych w latach 80. ... może jakiś mikrokontroler, który wie, że mam demencję,int
miał wtedy bardzo wyraźnie 16 bitów).Standard C ++ nie zadaje sobie trudu, aby określić, jakie
<cstdint>
typy są ani co gwarantują, po prostu wspomina „to samo co w C”.uint32_t
, zgodnie ze standardem C, gwarantuje uzyskanie dokładnie 32 bitów. Nic innego, nie mniej i żadnych bitów wypełniających. Czasami jest to dokładnie to, czego potrzebujesz, a zatem jest to bardzo cenne.uint_least32_t
gwarantuje, że niezależnie od rozmiaru, nie może być mniejszy niż 32 bity (ale równie dobrze mógłby być większy). Czasami, ale znacznie rzadziej niż dokładny witdh lub „nie obchodzi mnie”, właśnie tego chcesz.Wreszcie,
uint_fast32_t
moim zdaniem , jest nieco zbędny, z wyjątkiem celów związanych z udokumentowaniem intencji. Standard C stwierdza, że „wyznacza typ liczby całkowitej, który jest zwykle najszybszy” (zwróć uwagę na słowo „zwykle”) i wyraźnie stwierdza, że nie musi on być najszybszy do wszystkich celów. Innymi słowy,uint_fast32_t
jest prawie taki sam, jakuint_least32_t
, który zwykle jest najszybszy, z tą różnicą, że nie ma żadnej gwarancji (ale i tak żadnej gwarancji).Ponieważ przez większość czasu albo nie dbasz o dokładny rozmiar, albo potrzebujesz dokładnie 32 (lub 64, czasem 16) bitów, a ponieważ
unsigned int
typ „nie obchodzi” jest i tak najszybszy, to wyjaśnia, dlaczegouint_fast32_t
tak nie jest często używany.źródło
int
na 8-bitowych procesorach, nie pamiętam żadnego z tamtych czasów, który używał czegoś większego. Jeśli pamięć służy, kompilatory segmentowanej architektury x86 również używały 16-bitowejint
.int
w 68000 było to 32 bity (o czym pomyślałem jako przykład). To nie był ...int
w przeszłości miał być najszybszym typem z minimalną szerokością 16 bitów (dlatego C ma regułę promowania liczb całkowitych), ale dziś w przypadku architektur 64-bitowych nie jest to już prawdą. Na przykład 8-bajtowe liczby całkowite są szybsze niż 4-bajtowe liczby całkowite na bitach x86_64, ponieważ przy 4-bajtowych liczbach całkowitych kompilator musi wstawić dodatkową instrukcję, która interpretuje wartość 4-bajtową do wartości 8-bajtowej przed porównaniem jej z innymi wartościami 8-bajtowymi.long
, że ze względów historycznych musi być 32-bitowy, aint
teraz nie może być szerszy niżlong
, więcint
może być konieczne zachowanie 32-bitowego, nawet jeśli 64 bity byłyby szybsze.Nie widziałem dowodów, które
uint32_t
można by wykorzystać dla jego zakresu. Zamiast tego, przez większość czasu, który widziałem,uint32_t
jest używany do przechowywania dokładnie 4 oktetów danych w różnych algorytmach, z gwarantowaną semantyką zawijania i przesunięcia!Istnieją również inne powody, dla których warto używać
uint32_t
zamiastuint_fast32_t
: Często jest tak, że zapewnia stabilny ABI. Dodatkowo zużycie pamięci może być dokładnie znane. To bardzo kompensuje niezależnie od przyrostu prędkościuint_fast32_t
, jeśli ten typ będzie inny niż ten zuint32_t
.Dla wartości <65536 istnieje już poręczny typ, który jest wywoływany
unsigned int
(unsigned short
wymagane jest, aby mieć co najmniej ten zakres, aleunsigned int
ma on rodzimy rozmiar słowa). Dla wartości <4294967296 jest wywoływany innyunsigned long
.I wreszcie, ludzie nie używają,
uint_fast32_t
ponieważ wpisywanie jest irytująco długie i łatwe do wpisania: Dźródło
short
edycji.int
jest przypuszczalnie szybkim, jeśli różni się odshort
.unsigned int
zamiastuint16_fast_t
oznacza, że twierdzisz, że wiesz lepiej niż kompilator.unsigned long
nie jest dobrym wyborem, jeśli Twoja platforma ma 64-bitowe kartylong
i potrzebujesz tylko liczb<2^32
.uint16_t
iuint_fast16_t
. Gdybyuint_fast16_t
były bardziej luźne niż zwykłe typy liczb całkowitych, tak że jego zakres nie musi być spójny dla obiektów, których adresy nie są zajęte, mogłoby to zapewnić pewne korzyści w zakresie wydajności na platformach, które wykonują wewnętrznie 32-bitowe obliczenia arytmetyczne, ale mają 16-bitową magistralę danych . Norma nie pozwala jednak na taką elastyczność.Kilka powodów.
Podsumowując, „szybkie” typy to bezwartościowe śmieci. Jeśli naprawdę chcesz dowiedzieć się, jaki typ jest najszybszy dla danej aplikacji, musisz przetestować swój kod na swoim kompilatorze.
źródło
Z punktu widzenia poprawności i łatwości kodowania,
uint32_t
ma wiele zalet,uint_fast32_t
w szczególności ze względu na dokładniej zdefiniowany rozmiar i semantykę arytmetyczną, jak wskazało wielu użytkowników powyżej.Co być może już zaprzepaszczona, jest to, że jeden miał korzyść z
uint_fast32_t
- że może być szybciej , po prostu nigdy nie zmaterializowała się w żaden znaczący sposób. Większość 64-bitowych procesorów, które dominowały w erze 64-bitowej (głównie x86-64 i Aarch64) wyewoluowała z architektur 32-bitowych i ma szybkie 32-bitowe natywne operacje nawet w trybie 64-bitowym. Takuint_fast32_t
samo jakuint32_t
na tych platformach.Nawet jeśli niektóre z „działających również” platform, takich jak POWER, MIPS64, SPARC, oferują tylko 64-bitowe operacje ALU, zdecydowana większość interesujących operacji 32-bitowych można wykonać dobrze na rejestrach 64-bitowych: dolna wersja 32-bitowa mają pożądane rezultaty (a wszystkie popularne platformy pozwalają przynajmniej na ładowanie / przechowywanie 32-bitowych bitów). Przesunięcie w lewo jest głównym problemem, ale nawet to można w wielu przypadkach zoptymalizować przez optymalizację śledzenia wartości / zakresu w kompilatorze.
Wątpię, by sporadyczne nieco wolniejsze przesunięcie w lewo lub mnożenie 32x32 -> 64 przeważyło dwukrotnie nad zużyciem pamięci dla takich wartości we wszystkich, z wyjątkiem najbardziej niejasnych aplikacji.
Na koniec zauważę, że chociaż kompromis został w dużej mierze scharakteryzowany jako „wykorzystanie pamięci i potencjał wektoryzacji” (na korzyść
uint32_t
) w porównaniu z liczbą instrukcji / szybkością (na korzyśćuint_fast32_t
) - nawet to nie jest dla mnie jasne. Tak, na niektórych platformach będziesz potrzebować dodatkowych instrukcji dla niektórych operacji 32-bitowych, ale zapiszesz też niektóre instrukcje, ponieważ:struct two32{ uint32_t a, b; }
wrax
liketwo32{1, 2}
może zostać zoptymalizowane do postaci pojedynczej,mov rax, 0x20001
podczas gdy wersja 64-bitowa wymaga dwóch instrukcji. W zasadzie powinno to być również możliwe dla sąsiednich operacji arytmetycznych (ta sama operacja, inny operand), ale nie widziałem tego w praktyce.Mniejsze typy danych często wykorzystują lepsze nowoczesne konwencje wywoływania, takie jak SysV ABI, które wydajnie pakują dane struktury danych do rejestrów. Na przykład, możesz zwrócić do 16-bajtowej struktury w rejestrach
rdx:rax
. W przypadku funkcji zwracającej strukturę z 4uint32_t
wartościami (zainicjowaną ze stałej), co przekłada się naret_constant32(): movabs rax, 8589934593 movabs rdx, 17179869187 ret
Ta sama struktura z 4 64-bitowymi
uint_fast32_t
wymaga przesunięcia rejestru i czterech zapisów w pamięci, aby zrobić to samo (a wywołujący prawdopodobnie będzie musiał odczytać wartości z pamięci po powrocie):ret_constant64(): mov rax, rdi mov QWORD PTR [rdi], 1 mov QWORD PTR [rdi+8], 2 mov QWORD PTR [rdi+16], 3 mov QWORD PTR [rdi+24], 4 ret
Podobnie, podczas przekazywania argumentów struktury, 32-bitowe wartości są upakowane około dwa razy gęsto w rejestrach dostępnych dla parametrów, więc zmniejsza się prawdopodobieństwo, że zabraknie argumentów rejestrów i będziesz musiał przelać się na stos 1 .
Nawet jeśli zdecydujesz się używać
uint_fast32_t
w miejscach, w których „liczy się szybkość”, często będziesz mieć również miejsca, w których potrzebujesz stałego rozmiaru. Na przykład podczas przekazywania wartości do wyjścia zewnętrznego, z zewnętrznego wejścia, jako część twojego ABI, jako część struktury, która wymaga określonego układu lub ponieważ inteligentnie używaszuint32_t
do dużych agregacji wartości w celu zaoszczędzenia miejsca w pamięci. W miejscach, w których twojeuint_fast32_t
typy i `uint32_t` muszą się ze sobą łączyć, możesz znaleźć (oprócz złożoności programowania) niepotrzebne rozszerzenia znaków lub inny kod związany z niezgodnością rozmiaru. W wielu przypadkach kompilatory dobrze radzą sobie z optymalizacją tego rozwiązania, ale nadal nie jest niczym niezwykłym widzieć to w zoptymalizowanej wydajności podczas mieszania typów o różnych rozmiarach.Możesz pobawić się niektórymi z powyższych przykładów, a więcej na Godbolt .
1 Aby było jasne, konwencja upakowania struktur ciasno w rejestry nie zawsze jest oczywistą korzyścią dla mniejszych wartości. Oznacza to, że mniejsze wartości mogą wymagać „wyodrębnienia” przed ich użyciem. Na przykład prosta funkcja, która zwraca sumę dwóch składowych struktury razem, potrzebuje trochę
mov rax, rdi; shr rax, 32; add edi, eax
czasu dla wersji 64-bitowej, każdy argument otrzymuje własny rejestr i potrzebuje tylko jednegoadd
lublea
. Jeśli jednak zaakceptujesz, że projekt „ciasno upakowanych struktur podczas przechodzenia” ma ogólnie sens, mniejsze wartości będą bardziej wykorzystywać tę funkcję.źródło
uint_fast32_t
, co jest błędem IMO. (Najwyraźniej Windowsuint_fast32_t
jest typem 32-bitowym w systemie Windows.) Bycie 64-bitowym na Linuksie x86-64 jest powodem, dla którego nigdy nie polecałbym nikomu go używaćuint_fast32_t
: jest zoptymalizowany pod kątem małej liczby instrukcji (argumenty funkcji i wartości zwracane nigdy nie wymagają rozszerzenia zerowego dla użyj jako indeksu tablicy), a nie dla ogólnej szybkości lub rozmiaru kodu na jednej z głównych ważnych platform.pair<int,bool>
lubpair<int,int>
. Jeśli oba elementy członkowskie nie są stałymi czasu kompilacji, zwykle jest więcej niż tylko OR, a obiekt wywołujący musi rozpakować zwracane wartości. ( bugs.llvm.org/show_bug.cgi?id=34840 LLVM optymalizuje przekazywanie wartości zwracanej dla funkcji prywatnych i powinien traktować 32-bitowe int jako całość,rax
więcbool
jest oddzielnedl
zamiast 64-bitowej stałej dotest
it.)__attribute__((noinline))
dokładnie, aby upewnić się, że gcc nie wbudowuje funkcji obsługi błędów i umieszcza kilkapush rbx
/...
/pop rbx
/ ... na szybkiej ścieżce niektórych ważnych funkcji jądra, które mają wiele wywołań i same nie są wbudowane.Ze względów praktycznych
uint_fast32_t
jest całkowicie bezużyteczny. Jest on nieprawidłowo zdefiniowany na najbardziej rozpowszechnionej platformie (x86_64) i nigdzie nie oferuje żadnych korzyści, chyba że masz kompilator o bardzo niskiej jakości. Koncepcyjnie nigdy nie ma sensu używać „szybkich” typów w strukturach danych / tablicach - wszelkie oszczędności, które uzyskasz dzięki temu, że typ jest bardziej efektywny w działaniu, będą ograniczone kosztem (chybienia w pamięci podręcznej itp.) Zwiększania rozmiaru Twój zestaw danych roboczych. A dla indywidualnych zmiennych lokalnych (liczniki pętli, temps, itp.) Kompilator niebędący zabawką może zwykle po prostu pracować z większym typem w wygenerowanym kodzie, jeśli jest to bardziej wydajne, i skracać do rozmiaru nominalnego tylko wtedy, gdy jest to konieczne dla poprawności (iz podpisane typy, nigdy nie jest to konieczne).Jedynym wariantem, który jest teoretycznie przydatny, jest to
uint_least32_t
, że musisz mieć możliwość przechowywania dowolnej wartości 32-bitowej, ale chcesz być przenośny na maszyny, które nie mają typu 32-bitowego o dokładnym rozmiarze. Praktycznie jednak nie musisz się tym martwić.źródło
Według mojego zrozumienia
int
początkowo miał to być "natywny" typ liczby całkowitej z dodatkową gwarancją, że powinien mieć rozmiar co najmniej 16 bitów - coś, co było wtedy uważane za "rozsądny" rozmiar.Gdy platformy 32-bitowe stały się bardziej popularne, możemy powiedzieć, że „rozsądny” rozmiar zmienił się na 32 bity:
int
na wszystkich platformach.int
jest to co najmniej 32 bity.int
który gwarantuje dokładnie 32 bity.Ale kiedy platforma 64-bitowa stała się normą, nikt nie rozwinął się
int
do 64-bitowej liczby całkowitej z powodu:int
32-bitowego rozmiaru.int
może być nieracjonalne w większości przypadków, ponieważ w większości przypadków używane liczby są znacznie mniejsze niż 2 miliardy.Teraz, dlaczego wolisz
uint32_t
sięuint_fast32_t
? Z tego samego powodu języki, C # i Java zawsze używają liczb całkowitych o stałym rozmiarze: programista nie pisze kodu z myślą o możliwych rozmiarach różnych typów, piszą dla jednej platformy i testują kod na tej platformie. Większość kodu niejawnie zależy od określonych rozmiarów typów danych. I dlategouint32_t
w większości przypadków jest lepszym wyborem - nie pozwala na niejednoznaczność co do jego zachowania.Co więcej, czy
uint_fast32_t
rzeczywiście jest to najszybszy typ na platformie o rozmiarze równym lub większym niż 32 bity? Nie całkiem. Rozważmy ten kompilator kodu przez GCC dla x86_64 w systemie Windows:extern uint64_t get(void); uint64_t sum(uint64_t value) { return value + get(); }
Wygenerowany zespół wygląda następująco:
push %rbx sub $0x20,%rsp mov %rcx,%rbx callq d <sum+0xd> add %rbx,%rax add $0x20,%rsp pop %rbx retq
Teraz, jeśli zmienisz
get()
wartość zwracaną nauint_fast32_t
(czyli 4 bajty w systemie Windows x86_64), otrzymasz to:push %rbx sub $0x20,%rsp mov %rcx,%rbx callq d <sum+0xd> mov %eax,%eax ; <-- additional instruction add %rbx,%rax add $0x20,%rsp pop %rbx retq
Zwróć uwagę, że wygenerowany kod jest prawie taki sam, z wyjątkiem dodatkowych
mov %eax,%eax
instrukcji po wywołaniu funkcji, które mają na celu rozszerzenie wartości 32-bitowej do wartości 64-bitowej.Nie ma takiego problemu, jeśli używasz tylko wartości 32-bitowych, ale prawdopodobnie będziesz używać tych ze
size_t
zmiennymi (prawdopodobnie rozmiary tablic?), A są to 64 bity na x86_64. W Linuksieuint_fast32_t
jest 8 bajtów, więc sytuacja jest inna.Wielu programistów używa go,
int
gdy muszą zwrócić małą wartość (powiedzmy w zakresie [-32,32]). To działałoby idealnie, gdybyint
był to natywny rozmiar całkowitych platformy, ale ponieważ nie ma go na platformach 64-bitowych, lepszym wyborem jest inny typ, który pasuje do natywnego typu platformy (chyba że jest często używany z innymi liczbami całkowitymi o mniejszym rozmiarze).Zasadniczo, niezależnie od tego, co mówi standard, i tak
uint_fast32_t
jest zepsuty w niektórych implementacjach. Jeśli zależy Ci na dodatkowych instrukcjach generowanych w niektórych miejscach, powinieneś zdefiniować własny „natywny” typ liczby całkowitej. Lub możesz użyćsize_t
do tego celu, ponieważ zwykle będzie pasował donative
rozmiaru (nie uwzględniam starych i niejasnych platform, takich jak 8086, tylko platformy, które mogą obsługiwać Windows, Linux itp.).Innym znakiem, który pokazuje, że
int
powinien być natywny typ liczby całkowitej, jest „reguła promocji liczby całkowitej”. Większość procesorów może wykonywać operacje tylko na rodzimym komputerze, więc 32-bitowy procesor zwykle może wykonywać tylko 32-bitowe dodawanie, odejmowanie itp. (Wyjątkiem są procesory Intel). Typy liczb całkowitych innych rozmiarów są obsługiwane tylko przez instrukcje ładowania i przechowywania. Na przykład, wartość 8-bitowa powinna zostać załadowana odpowiednią instrukcją „załaduj 8-bitowy znak ze znakiem” lub „załaduj 8-bitowy bez znaku”, a po załadowaniu wartość zostanie rozszerzona do 32 bitów. Bez reguły C promowania liczb całkowitych kompilatory musiałyby dodać trochę więcej kodu dla wyrażeń, które używają typów mniejszych niż typ natywny. Niestety, nie ma to już miejsca w przypadku architektur 64-bitowych, ponieważ kompilatory muszą teraz w niektórych przypadkach emitować dodatkowe instrukcje (jak pokazano powyżej).źródło
uint_fast32_t
staje się niejednoznaczna we wszystkich, z wyjątkiem bardzo wybranych okoliczności. Podejrzewam, że głównym powodem jestuint_fastN_t
w ogóle dostosowanie się do „nie używajmy wersjiunsigned
64-bitowej, mimo że często jest to najszybsze na nowej platformie, ponieważ zbyt dużo kodu się zepsuje”, ale „nadal chcę szybkiego, co najmniej N-bitowego typu ”. Znów bym cię naświetlał, gdybym mógłint
64-bitowej nie powoduje prawie żadnego wzrostu szybkości , a zwykle jest to utrata szybkości wynikająca z rozmiaru pamięci podręcznej.int
wersję 64-bitową, twojauint32_t
czcionka o stałej szerokości wymagałaby__attribute__
hackowania lub innego hackowania lub jakiegoś niestandardowego typu, który jest mniejszy niżint
. (Alboshort
, ale wtedy masz ten sam problemuint16_t
.) Nikt tego nie chce. 32-bitowy jest wystarczająco szeroki dla prawie wszystkiego (w przeciwieństwie do 16-bitowego); używanie 32-bitowych liczb całkowitych, kiedy to wszystko, czego potrzebujesz, nie jest „nieefektywne” w żaden znaczący sposób na 64-bitowej maszynie.W wielu przypadkach, gdy algorytm działa na tablicy danych, najlepszym sposobem poprawy wydajności jest zminimalizowanie liczby błędów pamięci podręcznej. Im mniejszy każdy element, tym więcej z nich zmieści się w pamięci podręcznej. Dlatego nadal wiele kodu jest napisanych w celu używania wskaźników 32-bitowych na maszynach 64-bitowych: nie potrzebują one niczego bliskiego 4 GiB danych, ale koszt wykonania wszystkich wskaźników i przesunięć wymaga ośmiu bajtów zamiast czterech byłby znaczny.
Istnieją również pewne ABI i protokoły, które wymagają dokładnie 32 bitów, na przykład adresy IPv4. To
uint32_t
naprawdę oznacza: używaj dokładnie 32 bitów, niezależnie od tego, czy jest to wydajne dla procesora, czy nie. Kiedyś były one deklarowane jakolong
lubunsigned long
, co powodowało wiele problemów podczas przejścia na wersję 64-bitową. Jeśli potrzebujesz tylko typu bez znaku, który zawiera liczby do co najmniej 2³²-1, taka była definicja odunsigned long
czasu pojawienia się pierwszego standardu C. W praktyce jednak wystarczająco stary kod zakładał, że along
może zawierać dowolny wskaźnik, przesunięcie pliku lub znacznik czasu, a wystarczająco stary kod zakładał, że ma dokładnie 32 bity szerokości, że kompilatory niekoniecznie mogą zrobićlong
to samo, coint_fast32_t
bez zrywania zbyt wielu rzeczy.Teoretycznie byłoby bardziej przyszłościowe, aby program używał
uint_least32_t
, a może nawet ładowałuint_least32_t
elementy douint_fast32_t
zmiennej w celu obliczenia. Implementacja, która nie miała żadnegouint32_t
typu, mogłaby nawet deklarować formalną zgodność ze standardem! (To po prostu nie będzie w stanie skompilować wielu istniejących programów). W praktyce nie ma już więcej architektura gdzieint
,uint32_t
iuint_least32_t
nie są takie same, a nie zaleta, obecnie , do wykonaniauint_fast32_t
. Po co więc zbytnio komplikować?Jednak spójrz na powód, dla którego wszystkie
32_t
typy musiały istnieć, kiedy już mieliśmylong
, a zobaczysz, że te założenia już wcześniej wypłynęły nam na twarz. Twój kod może pewnego dnia zostać uruchomiony na komputerze, na którym obliczenia 32-bitowe o dokładnej szerokości są wolniejsze niż rodzimy rozmiar słowa, i lepiej byłoby używać gouint_least32_t
do przechowywania iuint_fast32_t
obliczeń religijnie. Lub jeśli przejdziesz przez ten most, kiedy do niego dojdziesz i po prostu chcesz czegoś prostego, to jestunsigned long
.źródło
int
nie ma 32 bitów, na przykład ILP64. Nie żeby były powszechne.int
będzie on miał 32 bity szerokości. RodzajMKL_INT
jest tym, co się zmieni.Aby udzielić bezpośredniej odpowiedzi: myślę, że prawdziwym powodem, dla którego
uint32_t
jest używany zbyt częstouint_fast32_t
lubuint_least32_t
po prostu jest to, że łatwiej jest pisać, a ponieważ jest krótszy, o wiele przyjemniejszy do czytania: jeśli tworzysz struktury z niektórymi typami, a niektóre z nich sąuint_fast32_t
lub podobne, wtedy często trudno jest ładnie dopasować je doint
lubbool
lub innych typów w C, które są dość krótkie (przykład:char
vs.character
). Oczywiście nie mogę tego poprzeć twardymi danymi, ale inne odpowiedzi mogą tylko zgadywać, dlaczego.Jeśli chodzi o względy techniczne
uint32_t
, nie sądzę, aby istniały żadne - kiedy absolutnie potrzebujesz dokładnego 32-bitowego int bez znaku, ten typ jest jedynym znormalizowanym wyborem. W prawie wszystkich innych przypadkach inne warianty są preferowane pod względem technicznym - szczególnie,uint_fast32_t
jeśli obawiasz się o szybkość iuint_least32_t
jeśli chodzi o przestrzeń dyskową. Używanieuint32_t
w obu tych przypadkach grozi niemożnością kompilacji, ponieważ typ nie musi istnieć.W praktyce
uint32_t
typy i pokrewne istnieją na wszystkich obecnych platformach, z wyjątkiem niektórych bardzo rzadkich (obecnie) DSP lub implementacji żartów, więc rzeczywiste ryzyko związane z użyciem tego typu jest niewielkie. Podobnie, chociaż możesz napotkać kary za prędkość przy typach o stałej szerokości, nie są one już (na nowoczesnych procesorach cpus) okaleczające.Dlatego uważam, że typ krótszy po prostu wygrywa w większości przypadków z powodu lenistwa programisty.
źródło