W dzisiejszym cross-platform C ++ (lub C) Świat możemy mieć :
Data model | short | int | long | long long | pointers/size_t | Sample operating systems
...
LLP64/IL32P64 16 32 32 64 64 Microsoft Windows (x86-64 and IA-64)
LP64/I32LP64 16 32 64 64 64 Most Unix and Unix-like systems, e.g. Solaris, Linux, BSD, and OS X; z/OS
...
Oznacza to dzisiaj, że dla każdej „wspólnej” (podpisanej) liczby całkowitej int
wystarczy i może być nadal używana jako domyślny typ liczby całkowitej podczas pisania kodu aplikacji C ++. Będzie również - dla obecnych praktycznych celów - mieć jednolity rozmiar na różnych platformach.
Jeśli przypadek użycia wymaga co najmniej 64 bitów, możemy dzisiaj użyć long long
, chociaż możliwe, że użyjemy jednego z typów określających bitowość lub __int64
typ może mieć większy sens.
Pozostaje to long
w środku i rozważamy całkowity zakaz korzystania long
z naszego kodu aplikacji .
Czy miałoby to sens , czy jest uzasadnione użycie long
nowoczesnego kodu C ++ (lub C), który musi działać na różnych platformach? (platforma to komputer stacjonarny, urządzenia mobilne, ale nie takie rzeczy jak mikrokontrolery, procesory DSP itp.)
Prawdopodobnie interesujące linki w tle:
- Jaki standard C ++ określa rozmiar int, długi typ?
- Dlaczego zespół Win64 wybrał model LLP64?
- 64-bitowe modele programowania: dlaczego LP64? (nieco w wieku)
- Czy
long
gwarantowane jest co najmniej 32 bity? (Dotyczy to dyskusji poniżej. Odpowiedź .)
źródło
long
to jedyny sposób na zagwarantowanie 32 bitów.int
może mieć 16 bitów, więc w niektórych aplikacjach to nie wystarczy. Tak,int
czasami jest 16 bitów w nowoczesnych kompilatorach. Tak, ludzie piszą oprogramowanie na mikrokontrolerach. Twierdziłbym, że więcej ludzi pisze oprogramowanie, które ma więcej użytkowników na mikrokontrolerach niż na PC, wraz z rozwojem urządzeń iPhone i Android, nie wspominając o rozwoju Arduinos itp.int
wciąż jest bardzo 16 bitów. Nienawidzę tego mówić, ale jeśli zamierzasz pisać o „dzisiejszym wieloplatformowym świecie”, nie możesz zignorować całego subkontynentu indyjskiego.Odpowiedzi:
Jedynym powodem, dla którego użyłbym
long
dzisiaj, jest wywołanie lub wdrożenie zewnętrznego interfejsu, który go używa.Jak powiedziałeś w swoim poście, krótkie i int mają dość stabilną charakterystykę na wszystkich głównych platformach komputerów stacjonarnych / serwerów / urządzeń mobilnych i nie widzę powodu, aby zmienić to w dającej się przewidzieć przyszłości. Więc nie widzę powodu, by w ogóle ich unikać.
long
z drugiej strony jest bałagan. Na wszystkich systemach 32-bitowych jestem tego świadomy, że miał następujące cechy.Duże ilości kodu zostały napisane w oparciu o jedną lub więcej z tych cech. Jednak po przejściu na wersję 64-bitową nie było możliwe zachowanie wszystkich z nich. Platformy uniksowe poszły na LP64, który zachował cechy 2 i 3 kosztem cechy 1. Win64 zdecydował się na LLP64, który zachował charakterystykę 1 kosztem cech 2 i 3. W rezultacie nie można już polegać na żadnej z tych cech i że IMO pozostawia niewiele powodów do użycia
long
.Jeśli chcesz mieć rozmiar dokładnie 32-bitowy, powinieneś użyć
int32_t
.Jeśli chcesz mieć czcionkę tego samego rozmiaru co wskaźnik, powinieneś użyć
intptr_t
(lub lepiejuintptr_t
).Jeśli chcesz typu, który jest największym przedmiotem, nad którym można pracować w jednym rejestrze / instrukcji, niestety nie sądzę, że standard go przewiduje.
size_t
powinien być odpowiedni na większości popularnych platform, ale nie byłby na x32 .PS
Nie zawracałbym sobie głowy typami „szybkimi” lub „najmniej”. Typy „najmniejsze” mają znaczenie tylko wtedy, gdy zależy Ci na przenośności, aby naprawdę zaciemnić architekturę
CHAR_BIT != 8
. Rozmiar „szybkich” typów w praktyce wydaje się dość arbitralny. Wydaje się, że Linux sprawia, że są co najmniej tego samego rozmiaru co wskaźnik, co jest głupie na 64-bitowych platformach z szybką obsługą 32-bitową, taką jak x86-64 i arm64. IIRC iOS czyni je tak małymi, jak to możliwe. Nie jestem pewien, co robią inne systemy.PPS
Jednym z powodów użycia
unsigned long
(ale nie prostegolong
) jest to, że zachowuje się modulo. Niestety ze względu na zepsute zasady promocji C typy niepodpisane mniejsze niżint
nie zachowują się modulo.Na wszystkich głównych platformach dzisiaj
uint32_t
jest tego samego rozmiaru lub większy niż int, a zatem ma zachowanie modulo. Jednak były historycznie i teoretycznie mogą istnieć na przyszłych platformach, gdzieint
jest 64-bitowy, a zatemuint32_t
nie ma zachowania modulo.Osobiście powiedziałbym, że lepiej jest wpaść w nawyk wymuszania zachowania modulo, używając „1u *” lub „0u +” na początku twoich równań, ponieważ będzie to działać dla dowolnego rozmiaru typu bez znaku.
źródło
Jak wspominasz w swoim pytaniu, nowoczesne oprogramowanie polega na współpracy między platformami i systemami w Internecie. Standardy C i C ++ podają zakresy rozmiarów liczb całkowitych, a nie konkretne rozmiary (w przeciwieństwie do języków takich jak Java i C #).
Aby mieć pewność, że twoje oprogramowanie skompilowane na różnych platformach działa z tymi samymi danymi w ten sam sposób i aby inne oprogramowanie mogło współpracować z twoim oprogramowaniem przy użyciu tych samych rozmiarów, powinieneś używać liczb całkowitych o stałym rozmiarze.
Wpisz,
<cstdint>
który zapewnia dokładnie to i jest standardowym nagłówkiem, który muszą zapewnić wszystkie kompilatory i standardowe platformy biblioteczne. Uwaga: ten nagłówek był wymagany tylko od C ++ 11, ale i tak zapewniało go wiele starszych implementacji bibliotek.Chcesz 64-bitową liczbę całkowitą bez znaku? Użyj
uint64_t
. Podpisano 32-bitową liczbę całkowitą? Użyjint32_t
. Chociaż typy w nagłówku są opcjonalne, nowoczesne platformy powinny obsługiwać wszystkie typy zdefiniowane w tym nagłówku.Czasami potrzebna jest określona szerokość bitów, na przykład w strukturze danych wykorzystywanej do komunikacji z innymi systemami. Innym razem tak nie jest. W mniej
<cstdint>
wymagających sytuacjach udostępnia typy o minimalnej szerokości.Istnieją najmniejsze warianty:
int_leastXX_t
będzie liczbą całkowitą zawierającą co najmniej XX bitów. Użyje najmniejszego typu, który zapewnia XX bitów, ale typ może być większy niż określona liczba bitów. W praktyce są one zwykle takie same jak typy opisane powyżej, które podają dokładną liczbę bitów.Istnieją również szybkie warianty:
int_fastXX_t
ma co najmniej XX bitów, ale powinien używać typu, który działa szybko na konkretnej platformie. Definicja „szybkiego” w tym kontekście jest nieokreślona. Jednak w praktyce zazwyczaj oznacza to, że typ mniejszy niż rozmiar rejestru procesora może być aliasem do typu rozmiaru rejestru procesora. Na przykład nagłówek programu Visual C ++ 2015 określaint_fast16_t
32-bitową liczbę całkowitą, ponieważ arytmetyka 32-bitowa jest ogólnie szybsza na x86 niż arytmetyka 16-bitowa.To wszystko jest ważne, ponieważ powinieneś być w stanie używać typów, które mogą przechowywać wyniki obliczeń wykonywanych przez Twój program niezależnie od platformy. Jeśli program generuje poprawne wyniki na jednej platformie, ale niepoprawne wyniki na innej z powodu różnic w przepełnieniu liczb całkowitych, to źle. Używając standardowych typów liczb całkowitych, gwarantujesz, że wyniki na różnych platformach będą takie same w odniesieniu do wielkości użytych liczb całkowitych (oczywiście mogą występować inne różnice między platformami poza szerokością całkowitą).
Tak,
long
powinien zostać zablokowany w nowoczesnym kodzie C ++. Więc należyint
,short
ilong long
.źródło
std
przestrzeni nazw, gdy#include
d jest w jednostce kompilacji C ++, ale dokumentacja, którą podłączyłem, nie wspomina o tym, a Visual Studio wydaje się nie dbać o to, jak do nich uzyskać dostęp.int
może być ... nadmierny? (Zastanowiłbym się, czy kod musi być wyjątkowo przenośny na wszystkich niejasnych (i nie tak niejasnych) platformach. Zakazanie go dla „kodu aplikacji” może nie sprzyjać naszym deweloperom.#include <cstdint>
jest zobowiązany do umieszczenia typówstd::
i (niestety) opcjonalnie dozwolone jest również umieszczenie ich w globalnej przestrzeni nazw.#include <stdint.h>
jest dokładnie odwrotnie. To samo dotyczy każdej innej pary nagłówków C. Zobacz: stackoverflow.com/a/13643019/2757035 Chciałbym, żeby Standard wymagał, aby każdy z nich wpływał tylko na odpowiednią wymaganą przestrzeń nazw - zamiast pozornie wypaczać złe konwencje ustanowione przez niektóre implementacje - ale cóż, oto jesteśmy.Nie, zakazanie wbudowanych typów całkowitych byłoby absurdalne. Nie należy ich jednak nadużywać.
Jeśli potrzebujesz liczby całkowitej o szerokości dokładnie N bitów, użyj (lub jeśli potrzebujesz wersji). Myślenie o 32-bitowej liczbie całkowitej i 64-bitowej liczbie całkowitej jest po prostu błędne. Może się tak zdarzyć na obecnych platformach, ale zależy to od zachowania zdefiniowanego w implementacji.
std::intN_t
std::uintN_t
unsigned
int
long long
Używanie typów liczb całkowitych o stałej szerokości jest również przydatne do współpracy z innymi technologiami. Na przykład, jeśli niektóre części aplikacji są napisane w Javie, a inne w C ++, prawdopodobnie będziesz chciał dopasować typy liczb całkowitych, aby uzyskać spójne wyniki. (Nadal pamiętaj, że przepełnienie w Javie ma dobrze zdefiniowaną semantykę, podczas gdy
signed
przepełnienie w C ++ jest niezdefiniowanym zachowaniem, więc spójność jest najwyższym celem.) Będą one również nieocenione przy wymianie danych między różnymi hostami obliczeniowymi.Jeśli nie potrzebujesz dokładnie N bitów, a tylko wystarczająco szeroki typ , rozważ użycie (zoptymalizowanego pod kątem miejsca) lub (zoptymalizowanego pod kątem prędkości). Ponownie, obie rodziny też mają odpowiedniki.
std::int_leastN_t
std::int_fastN_t
unsigned
Kiedy więc używać wbudowanych typów? Ponieważ standard nie precyzuje dokładnie ich szerokości, używaj ich, gdy nie zależy ci na rzeczywistej szerokości bitów, ale na innych cechach.
A
char
jest najmniejszą liczbą całkowitą adresowaną przez sprzęt. Język faktycznie zmusza cię do użycia go do aliasingu dowolnej pamięci. Jest to również jedyny możliwy rodzaj reprezentacji (wąskich) ciągów znaków.int
Zazwyczaj będzie najszybsza typu urządzenie może obsłużyć. Będzie wystarczająco szeroki, aby można go było ładować i przechowywać za pomocą jednej instrukcji (bez maskowania lub przesuwania bitów) i wystarczająco wąski, aby można go było obsługiwać za pomocą (najbardziej) wydajnych instrukcji sprzętowych. Dlategoint
jest idealnym wyborem do przekazywania danych i wykonywania arytmetyki, gdy przepełnienie nie stanowi problemu. Na przykład domyślnym typem wyliczeń jestint
. Nie zmieniaj go na 32-bitową liczbę całkowitą tylko dlatego, że możesz. Ponadto, jeśli masz wartość, która może wynosić tylko –1, 0 i 1, anint
to idealny wybór, chyba że masz zamiar przechowywać ich ogromne tablice. W takim przypadku możesz użyć bardziej zwartego typu danych, kosztem zapłacenia wyższej ceny za dostęp do poszczególnych elementów. Wydajniejsze buforowanie prawdopodobnie się za to opłaci. Wiele funkcji systemu operacyjnego jest również zdefiniowanych w kategoriachint
. Głupio byłoby przełożyć ich argumenty i wyniki tam iz powrotem. To wszystko, co prawdopodobnie może zrobić, to wprowadzić błędy przepełnienia.long
będzie zwykle najszerszym typem, który można obsłużyć za pomocą instrukcji dla jednej maszyny. To sprawia, że jest szczególnieunsigned long
atrakcyjna do radzenia sobie z surowymi danymi i wszelkiego rodzaju manipulacjami bitowymi. Na przykład spodziewałbym się zobaczyćunsigned long
implementację wektora bitowego. Jeśli kod jest napisany ostrożnie, nie ma znaczenia, jak szeroki jest rzeczywiście typ (ponieważ kod dostosuje się automatycznie). Na platformach, na których natywnym słowem maszynowym jest 32 bity, tablica podkładowa wektora bitowego może być tablicąunsigned
32-bitowe liczby całkowite są najbardziej pożądane, ponieważ głupio byłoby użyć typu 64-bitowego, który musi być ładowany za pomocą drogich instrukcji tylko w celu przesunięcia i maskowania niepotrzebnych bitów. Z drugiej strony, jeśli rodzimy rozmiar słowa platformy wynosi 64 bity, chcę tablicę tego typu, ponieważ oznacza to, że operacje takie jak „znajdź pierwszy zestaw” mogą działać nawet dwukrotnie szybciej. Zatem „problem”long
opisywanego typu danych, którego rozmiar różni się w zależności od platformy, w rzeczywistości jest funkcją, którą można dobrze wykorzystać. Staje się to problemem tylko wtedy, gdy myślisz o wbudowanych typach jako typach o określonej szerokości bitów, których po prostu nie mają.char
,int
Ilong
są bardzo użyteczne typów, jak opisano powyżej.short
ilong long
nie są prawie tak przydatne, ponieważ ich semantyka jest znacznie mniej wyraźna.źródło
long
Windowsa i Uniksa. Mogę być nieporozumieniem, ale twój opis różnicy w wielkościlong
bycia „funkcją” zamiast „problemu” ma dla mnie sens przy porównywaniu 32- i 64-bitowych modeli danych, ale nie dla tego konkretnego porównania. Czy w tej konkretnej sprawie pytanie jest naprawdę takie? Czy może jest to cecha w innych sytuacjach (ogólnie) i nieszkodliwe w tym przypadku?uint32_t
wartości będzie przeprowadzana jako podpisana ,int
arytmetyka szerokości na platformie, któraint
jest szersza niżuint32_t
. (Przy dzisiejszych ABI jest to o wiele bardziej prawdopodobne, że będzie to problemuint16_t
.)long
zwykle będzie najszerszym typem, który można obsłużyć za pomocą instrukcji dla pojedynczej maszyny. ...” - i to jest całkowicie błędne . Spójrz na model danych Windows. IMHO, cały twój poniższy przykład się psuje, ponieważ w systemie Windows x64 długi jest nadal 32-bitowy.Inna odpowiedź już omawia typy cstdint i ich mniej znane odmiany.
Chciałbym dodać do tego:
użyj nazw typów specyficznych dla domeny
Oznacza to, że nie deklarują swoje parametry i zmienne być
uint32_t
(na pewno nielong
!), Ale nazwy takie jakchannel_id_type
,room_count_type
etc.o bibliotekach
Biblioteki stron trzecich, które używają
long
lub nie, mogą być denerwujące, szczególnie jeśli są używane jako odniesienia lub wskaźniki do nich.Najlepszą rzeczą jest, aby owijarki.
Ogólnie rzecz biorąc, moją strategią jest stworzenie zestawu podobnych do obsady funkcji, które będą używane. Są przeciążone, aby zaakceptować tylko te typy, które dokładnie pasują do odpowiednich typów, wraz ze wszystkimi potrzebnymi wskaźnikami itp. Są zdefiniowane specyficznie dla systemu operacyjnego / kompilatora / ustawień. Pozwala to usunąć ostrzeżenia, a jednocześnie zapewnić, że używane są tylko „prawidłowe” konwersje.
W szczególności, gdy różne typy pierwotne generują 32 bity, wybór sposobu
int32_t
zdefiniowania może nie pasować do wywołania biblioteki (np. Int vs long w systemie Windows).Funkcja rzutowania dokumentuje kolizję, umożliwia sprawdzanie w czasie kompilacji wyniku pasującego do parametru funkcji i usuwa wszelkie ostrzeżenia lub błędy, jeśli tylko wtedy, gdy rzeczywisty typ odpowiada rzeczywistemu rozmiarowi. Oznacza to, że jest przeciążony i zdefiniowany, jeśli przekażę (w systemie Windows) an
int*
lub along*
i w przeciwnym razie poda błąd czasu kompilacji.Tak więc, jeśli biblioteka zostanie zaktualizowana lub ktoś zmieni to
channel_id_type
, co jest, jest to nadal weryfikowane.źródło