long long int vs. long int vs. int64_t in C ++


87

Ho sperimentato un comportamento strano durante l'utilizzo di tratti di tipo C ++ e ho ristretto il mio problema a questo piccolo problema eccentrico per il quale darò un sacco di spiegazioni poiché non voglio lasciare nulla di aperto per interpretazioni errate.

Supponi di avere un programma come questo:

#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;
}

In entrambe le compilazioni a 32 bit con GCC (e con MSVC a 32 e 64 bit), l'output del programma sarà:

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

Tuttavia, il programma risultante da una compilazione GCC a 64 bit produrrà:

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

Questo è curioso, poiché long long intè un intero con segno a 64 bit ed è, a tutti gli effetti, identico ai tipi long inte int64_t, quindi logicamente int64_t, long inte long long intsarebbero tipi equivalenti: l'assembly generato quando si utilizzano questi tipi è identico. Uno sguardo a stdint.hme mi dice perché:

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

In un 64-bit compilazione, int64_tè long int, non long long int(ovviamente).

La soluzione per questa situazione è piuttosto semplice:

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

Ma questo è orribilmente hacker e non scala bene (funzioni effettive della sostanza uint64_t, ecc.). Quindi la mia domanda è: c'è un modo per dire al compilatore che a long long intè anche a int64_t, proprio come long intè?


I miei pensieri iniziali sono che questo non è possibile, a causa del modo in cui funzionano le definizioni di tipo C / C ++. Non c'è un modo per specificare l'equivalenza del tipo dei tipi di dati di base al compilatore, poiché questo è il lavoro del compilatore (e consentire ciò potrebbe rompere molte cose) e typedefva solo in un modo.

Inoltre, non sono troppo preoccupato di ottenere una risposta qui, dal momento che questo è un caso limite super-duper di cui non sospetto che a nessuno interesserà mai quando gli esempi non sono orribilmente inventati (significa questo dovrebbe essere un wiki della comunità?) .


Aggiungi : il motivo per cui sto utilizzando la specializzazione parziale del modello invece di un esempio più semplice come:

void go(int64_t) { }

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

è che detto esempio verrà comunque compilato, poiché long long intè implicitamente convertibile in un file int64_t.


Aggiungi : l'unica risposta fino ad ora presuppone che io voglia sapere se un tipo è a 64 bit. Non volevo indurre le persone a pensare che ci tenessi a questo e probabilmente avrei dovuto fornire più esempi di dove si manifesta questo problema.

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

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

In questo esempio, some_type_trait<long int>sarà a boost::true_type, ma some_type_trait<long long int>non lo sarà. Sebbene questo abbia senso nell'idea di tipi di C ++, non è desiderabile.

Un altro esempio sta usando un qualificatore come same_type(che è abbastanza comune da usare in C ++ 0x Concepts):

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

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

Quell'esempio non viene compilato, poiché C ++ (correttamente) vede che i tipi sono diversi. g ++ non riuscirà a compilare con un errore come: nessuna chiamata di funzione corrispondente same_type(long int&, long long int&).

Vorrei sottolineare che capisco perché sta accadendo, ma sto cercando una soluzione alternativa che non mi costringa a ripetere il codice ovunque.


Per curiosità, il tuo programma di esempio fornisce gli stessi risultati per sizeofogni tipo? Forse il compilatore tratta la dimensione di in modo long long intdiverso.
— Blair Holloway

Hai compilato con C ++ 0x abilitato? C ++ 03 non ha <cstdint>, quindi forse il fatto che deve dire "questa è un'estensione" (che è) la sta cercando.
— GManNickG

Sì, probabilmente avrei dovuto specificare che sto usando --std=c++0x. E sì, sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8.
— Travis Gockel

1
Nessuno lo ha ancora menzionato, ma nel caso fosse trascurato: longe long longsono tipi distinti (anche se hanno la stessa dimensione e rappresentazione). int64_tè sempre un alias per un altro tipo esistente (nonostante il nome, typedefnon crea nuovi tipi, assegna solo un alias a uno già esistente)
— MM

3
Nelle risposte / commenti manca un'affermazione importante, che mi ha aiutato quando questa stranezza mi ha colpito: non usare mai tipi a dimensione fissa per modelli specializzati in modo affidabile. Usa sempre i tipi di base e copri tutti i casi possibili (anche se utilizzi tipi a dimensione fissa per istanziare quei modelli). Tutti i casi possibili significa: se è necessario creare un'istanza con int16_t, specializzarsi con shorte inte sarai coperto. (e con signed charse ti senti avventuroso)
— Irfy

Risposte:


49

Non è necessario passare a 64 bit per vedere qualcosa di simile. Considera le int32_tcomuni piattaforme a 32 bit. Potrebbe essere modificato typedefcome into come long, ma ovviamente solo uno dei due alla volta. inte longsono ovviamente tipi distinti.

Non è difficile vedere che non ci sono soluzioni alternative int == int32_t == longper i sistemi a 32 bit. Per lo stesso motivo, non è possibile eseguire operazioni long == int64_t == long longsu sistemi a 64 bit.

Se si potesse, le possibili conseguenze sarebbero piuttosto doloroso per il codice che sovraccarica foo(int), foo(long)e foo(long long)- improvvisamente avrebbero avuto due definizioni per la stessa di sovraccarico ?!

La soluzione corretta è che il codice del modello di solito non dovrebbe fare affidamento su un tipo preciso, ma sulle proprietà di quel tipo. L'intera same_typelogica potrebbe ancora essere OK per casi specifici:

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

Cioè, il sovraccarico foo(int64_t) non è definito quando è esattamente lo stesso di foo(long).

[modifica] Con C ++ 11, ora abbiamo un modo standard per scrivere questo:

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

[modifica] O C ++ 20

long foo(long x);
int64_t foo(int64_t) requires (!std::is_same_v<int64_t, long>);

1
La notizia triste è, ad esempio, su MSVC19 a 64 bit (2017) sizeof() longed intè identica, ma std::is_same<long, int>::valueritorna false. Stessa stranezza su AppleClang 9.1 su OSX HighSierra.
— Ax3l

3
@ Ax3l: Non è strano. Praticamente ogni compilatore dall'ISO C 90 ha almeno una di queste coppie.
— MSalters

È vero, sono tipi distinti.
— Ax3l

6

Vuoi sapere se un tipo è lo stesso tipo di int64_t o vuoi sapere se qualcosa è a 64 bit? Sulla base della tua soluzione proposta, penso che tu stia chiedendo di quest'ultima. In tal caso, farei qualcosa di simile

template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64

1
Non ti manca returnun punto e virgola?
— casablanca

1
Tuttavia, dovresti usarlo sizeofper questo.
— Ben Voigt

5
long long int e long int non sono dello stesso tipo indipendentemente dal fatto che abbiano o meno la stessa dimensione. Il comportamento non è errato. Questo è solo C ++.
— Logan Capaldo

5
Non è una limitazione della digitazione nominale. È una limitazione della digitazione nominale senza senso . Ai vecchi tempi, lo standard de facto era short= 16 bit, long= 32 bit e int= dimensione nativa. In questi giorni a 64 bit, inte longnon significano più niente.
— dan04

1
@ dan04: Non sono più o meno significativi di quanto siano mai stati. shortè di almeno 16 bit, intè almeno 16 bit ed longè almeno 32 bit, con (segue notazione sciatta) short <= int <= long. I "vecchi tempi" a cui ti riferisci non sono mai esistiti; ci sono sempre state variazioni all'interno delle restrizioni imposte dalla lingua. L'errore "Tutto il mondo è un x86" è pericoloso quanto il vecchio "Tutto il mondo è un errore VAX.
— Keith Thompson

1

Quindi la mia domanda è: c'è un modo per dire al compilatore che un long long int è anche un int64_t, proprio come lo è long int?

Questa è una buona domanda o problema, ma sospetto che la risposta sia NO.

Inoltre, a long intpotrebbe non essere a long long int.


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

Credo che questo sia libc. Sospetto che tu voglia andare più a fondo.

In entrambe le compilazioni a 32 bit con GCC (e con MSVC a 32 e 64 bit), l'output del programma sarà:

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

Linux a 32 bit utilizza il modello dati ILP32. I numeri interi, i long e i puntatori sono a 32 bit. Il tipo a 64 bit è un file long long.

Microsoft documenta gli intervalli in intervalli di tipi di dati . Diciamo che long longè equivalente a __int64.

Tuttavia, il programma risultante da una compilazione GCC a 64 bit produrrà:

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

Linux a 64 bit utilizza il LP64modello di dati. I lunghi sono a 64 bit e long longsono a 64 bit. Come con 32 bit, Microsoft documenta gli intervalli in Data Type Ranges e long long è ancora __int64.

C'è un ILP64modello di dati in cui tutto è a 64 bit. Devi fare del lavoro extra per ottenere una definizione per il tuo word32tipo. Vedi anche articoli come Modelli di programmazione a 64 bit: Perché LP64?


Ma questo è orribilmente hacker e non scala bene (funzioni effettive della sostanza, uint64_t, ecc.) ...

Sì, c'è di meglio. GCC combina e confronta dichiarazioni che dovrebbero accettare tipi a 64 bit, quindi è facile mettersi nei guai anche se si segue un particolare modello di dati. Ad esempio, quanto segue causa un errore di compilazione e ti dice di usare -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);

Risulta in:

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

Quindi, ignoralo LP64e modificalo in:

typedef unsigned long long word64;

Quindi, passa a un gadget ARM IoT a 64 bit che definisce LP64e utilizza NEON:

error: invalid conversion from `word64* {aka long long unsigned int*}' to `uint64_t*'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.