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.