long long int vs. long int vs. int64_t w C ++

87

Doświadczyłem dziwnego zachowania podczas używania cech typu C ++ i zawęziłem swój problem do tego dziwacznego małego problemu, dla którego dam mnóstwo wyjaśnień, ponieważ nie chcę zostawiać niczego otwartego na błędną interpretację.

Powiedzmy, że masz taki program:

#include <iostream>
#include <cstdint>

template <typename T>
bool is_int64() { return false; }

template <>
bool is_int64<int64_t>() { return true; }

int main()
{
 std::cout << "int:\t" << is_int64<int>() << std::endl;
 std::cout << "int64_t:\t" << is_int64<int64_t>() << std::endl;
 std::cout << "long int:\t" << is_int64<long int>() << std::endl;
 std::cout << "long long int:\t" << is_int64<long long int>() << std::endl;

 return 0;
}

W obu 32-bitowych kompilacjach z GCC (oraz z 32- i 64-bitowym MSVC), wyjście programu będzie:

int:           0
int64_t:       1
long int:      0
long long int: 1

Jednak program powstały z 64-bitowej kompilacji GCC wyświetli:

int:           0
int64_t:       1
long int:      1
long long int: 0

Jest to ciekawe, ponieważ long long intjest to 64-bitowa liczba całkowita ze znakiem i pod każdym względem jest identyczna z typami long inti int64_t, więc logicznie int64_t, long inti long long intbyłyby równoważnymi typami - zestaw generowany podczas korzystania z tych typów jest identyczny. Jedno spojrzenie stdint.hmówi mi, dlaczego:

# if __WORDSIZE == 64
typedef long int  int64_t;
# else
__extension__
typedef long long int  int64_t;
# endif

W 64-bitowym kompilacji, int64_tto long int, a nie long long int(oczywiście).

Rozwiązanie tej sytuacji jest dość łatwe:

#if defined(__GNUC__) && (__WORDSIZE == 64)
template <>
bool is_int64<long long int>() { return true; }
#endif

Ale to jest strasznie hakerskie i nie skaluje się dobrze (rzeczywiste funkcje substancji uint64_titp.). Więc moje pytanie brzmi: czy istnieje sposób, aby powiedzieć kompilatorowi, że a long long intjest również a int64_t, tak jak long intjest?


Moje początkowe przemyślenia są takie, że nie jest to możliwe ze względu na sposób działania definicji typów C / C ++. Nie ma sposobu, aby określić równoważność typów podstawowych typów danych kompilatorowi, ponieważ jest to zadanie kompilatora (i pozwolenie na to może zepsuć wiele rzeczy) i działa typedeftylko w jedną stronę.

Nie jestem też zbyt zainteresowany uzyskaniem odpowiedzi tutaj, ponieważ jest to super-duper edge przypadek, o którym nie podejrzewam, że ktoś kiedykolwiek będzie się przejmował, gdy przykłady nie są okropnie wymyślone (czy to oznacza, że ​​powinno to być wiki społeczności?) .


Dołącz : powód, dla którego używam częściowej specjalizacji szablonu zamiast prostszego przykładu, takiego jak:

void go(int64_t) { }

int main()
{
    long long int x = 2;
    go(x);
    return 0;
}

jest to, że wspomniany przykład będzie nadal kompilowany, ponieważ long long intjest niejawnie konwertowany na plik int64_t.


Dołącz : jedyna jak dotąd odpowiedź zakłada, że ​​chcę wiedzieć, czy typ jest 64-bitowy. Nie chciałem wprowadzać ludzi w błąd, aby myśleli, że mi na tym zależy i prawdopodobnie powinienem był podać więcej przykładów, gdzie ten problem się objawia.

template <typename T>
struct some_type_trait : boost::false_type { };

template <>
struct some_type_trait<int64_t> : boost::true_type { };

W tym przykładzie some_type_trait<long int>będzie boost::true_type, ale some_type_trait<long long int>nie będzie. Chociaż ma to sens w koncepcji typów w C ++, nie jest pożądane.

Innym przykładem jest użycie kwalifikatora takiego jak same_type(który jest dość powszechny w C ++ 0x Pojęcia):

template <typename T>
void same_type(T, T) { }

void foo()
{
    long int x;
    long long int y;
    same_type(x, y);
}

Ten przykład nie można skompilować, ponieważ C ++ (poprawnie) widzi, że typy są różne. g ++ nie skompiluje się z błędem takim jak: brak dopasowania funkcji same_type(long int&, long long int&).

Chciałbym podkreślić, że rozumiem, dlaczego tak się dzieje, ale szukam obejścia, które nie zmusza mnie do powtarzania kodu w każdym miejscu.

Travis Gockel
źródło
Z ciekawości, czy Twój przykładowy program daje takie same wyniki dla sizeofkażdego typu? Być może kompilator long long intinaczej traktuje rozmiar .
Blair Holloway,
Czy kompilowałeś z włączonym C ++ 0x? C ++ 03 nie ma <cstdint>, więc być może fakt, że musi powiedzieć „to jest rozszerzenie” (którym jest), jest dla niego sprzeczny.
GManNickG,
Tak, prawdopodobnie powinienem był określić, że używam --std=c++0x. I tak, sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8.
Travis Gockel
1
Nikt jeszcze o tym nie wspomniał, ale na wypadek, gdyby zostało to przeoczone: longi long longsą to różne typy (nawet jeśli mają tę samą wielkość i reprezentację). int64_tjest zawsze aliasem dla innego istniejącego typu (pomimo swojej nazwy typedefnie tworzy nowych typów, po prostu nadaje alias do już istniejącego)
MM
3
W odpowiedziach / komentarzach brakuje jednego ważnego stwierdzenia, które pomogło mi, gdy uderzyło mnie to dziwactwo: Nigdy nie używaj typów o stałym rozmiarze do niezawodnej specjalizacji szablonów. Zawsze używaj podstawowych typów i obejmuj wszystkie możliwe przypadki (nawet jeśli używasz typów o stałym rozmiarze do tworzenia wystąpienia tych szablonów). Wszystkie możliwe przypadki oznaczają: jeśli musisz utworzyć wystąpienie z int16_t, specjalizuj się w shorti, inta będziesz objęty ochroną. (i signed charjeśli czujesz się na
siłach

Odpowiedzi:

49

Nie musisz przechodzić do wersji 64-bitowej, aby zobaczyć coś takiego. Weź int32_tpod uwagę typowe platformy 32-bitowe. Może być typedefwydany jako intlub jako long, ale oczywiście tylko jeden z dwóch naraz. inti longoczywiście są to różne typy.

Nietrudno zauważyć, że nie ma obejścia, które działa int == int32_t == longna systemach 32-bitowych. Z tego samego powodu nie ma możliwości tworzenia long == int64_t == long longna systemach 64-bitowych.

Jeśli mógł, ewentualne konsekwencje byłyby dość bolesne dla kodu, który przeciążone foo(int), foo(long)i foo(long long)- nagle mieliby dwie definicje tego samego przeciążenia ?!

Prawidłowym rozwiązaniem jest to, że kod szablonu zwykle nie powinien opierać się na dokładnym typie, ale na właściwościach tego typu. Cała same_typelogika może nadal działać w określonych przypadkach:

long foo(long x);
std::tr1::disable_if(same_type(int64_t, long), int64_t)::type foo(int64_t);

Oznacza to, że przeciążenie foo(int64_t)nie jest zdefiniowane, gdy jest dokładnie takie samo jak foo(long).

[edytuj] W C ++ 11 mamy teraz standardowy sposób pisania tego:

long foo(long x);
std::enable_if<!std::is_same<int64_t, long>::value, int64_t>::type foo(int64_t);

[edytuj] Lub C ++ 20

long foo(long x);
int64_t foo(int64_t) requires (!std::is_same_v<int64_t, long>);
MSalters
źródło
1
Smutna wiadomość jest np. Na 64-bitowym MSVC19 (2017) sizeof() longi intjest identyczna, ale std::is_same<long, int>::valuewraca false. Ta sama dziwność w AppleClang 9.1 na OSX HighSierra.
Ax3l
3
@ Ax3l: To nie jest dziwne. Praktycznie każdy kompilator od ISO C 90 ma przynajmniej jedną taką parę.
MSalters,
To prawda, są to różne typy.
Ax3l
6

Czy chcesz wiedzieć, czy typ jest tego samego typu co int64_t, czy chcesz wiedzieć, czy coś ma 64 bity? Opierając się na proponowanym przez Ciebie rozwiązaniu, myślę, że pytasz o to drugie. W takim razie zrobiłbym coś takiego

template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
Logan Capaldo
źródło
1
Czy nie brakuje ci returni średnika?
casablanca,
1
Mimo to powinieneś używać sizeofdo tego.
Ben Voigt,
5
long long int i long int nie są tego samego typu, niezależnie od tego, czy mają ten sam rozmiar. Zachowanie nie jest błędne. To po prostu C ++.
Logan Capaldo
5
To nie jest ograniczenie pisania nominalnego. To ograniczenie bezsensownego pisania nominalnego. W dawnych czasach de facto standardem był short= 16 bitów, long= 32 bity i int= rozmiar natywny. W dzisiejszych czasach 64-bitowych inti już longnic nie znaczą .
dan04
1
@ dan04: Nie są mniej lub bardziej znaczące niż kiedykolwiek. shortma co najmniej 16 bitów, intma co najmniej 16 bitów i longma co najmniej 32 bity, z (następującą notacją niechlujną) short <= int <= long. „Stare czasy”, o których mówisz, nigdy nie istniały; zawsze istniały różnice w ograniczeniach narzuconych przez język. Błąd „Cały świat x86” jest tak samo niebezpieczny jak starszy „Błąd„ Cały świat to błąd VAX ”.
Keith Thompson,
1

Więc moje pytanie brzmi: czy istnieje sposób, aby powiedzieć kompilatorowi, że long long int jest również int64_t, tak jak long int?

To dobre pytanie lub problem, ale podejrzewam, że odpowiedź brzmi NIE.

Ponadto a long intnie może być long long int.


# if __WORDSIZE == 64
typedef long int  int64_t;
# else
__extension__
typedef long long int  int64_t;
# endif

Uważam, że to jest libc. Podejrzewam, że chcesz wejść głębiej.

W obu 32-bitowych kompilacjach z GCC (oraz z 32- i 64-bitowym MSVC), wyjście programu będzie:

int:           0
int64_t:       1
long int:      0
long long int: 1

32-bitowy Linux używa modelu danych ILP32. Liczby całkowite, długie i wskaźniki są 32-bitowe. Typ 64-bitowy to long long.

Firma Microsoft dokumentuje zakresy w zakresach typów danych . Powiedz, że long longjest równoważne __int64.

Jednak program powstały z 64-bitowej kompilacji GCC wyświetli:

int:           0
int64_t:       1
long int:      1
long long int: 0

64-bitowy Linux używa LP64modelu danych. Długie są 64-bitowe i long long64-bitowe. Podobnie jak w przypadku wersji 32-bitowej, Microsoft dokumentuje zakresy w zakresach typów danych, a long long jest nadal __int64.

Istnieje ILP64model danych, w którym wszystko jest 64-bitowe. Musisz wykonać dodatkową pracę, aby uzyskać definicję swojego word32typu. Zobacz także artykuły, takie jak 64-bitowe modele programowania: Dlaczego LP64?


Ale to jest okropnie hakerskie i nie skaluje się dobrze (rzeczywiste funkcje substancji, uint64_t itp.) ...

Tak, jest jeszcze lepiej. GCC miesza i dopasowuje deklaracje, które mają przyjmować typy 64-bitowe, więc łatwo jest wpaść w kłopoty, nawet jeśli podążasz za określonym modelem danych. Na przykład poniższe powoduje błąd kompilacji i nakazuje użycie -fpermissive:

#if __LP64__
typedef unsigned long word64;
#else
typedef unsigned long long word64;
#endif

// intel definition of rdrand64_step (http://software.intel.com/en-us/node/523864)
// extern int _rdrand64_step(unsigned __int64 *random_val);

// Try it:
word64 val;
int res = rdrand64_step(&val);

Skutkuje to:

error: invalid conversion from `word64* {aka long unsigned int*}' to `long long unsigned int*'

Więc zignoruj LP64i zmień to na:

typedef unsigned long long word64;

Następnie przejdź do 64-bitowego gadżetu ARM IoT, który definiuje LP64i używa NEON:

error: invalid conversion from `word64* {aka long long unsigned int*}' to `uint64_t*'
jww
źródło