int panjang vs int panjang vs int64_t di C ++

87

Saya mengalami beberapa perilaku aneh saat menggunakan ciri tipe C ++ dan telah mempersempit masalah saya ke masalah kecil yang unik ini yang akan saya berikan banyak penjelasan karena saya tidak ingin membiarkan sesuatu terbuka untuk salah tafsir.

Katakanlah Anda memiliki program seperti ini:

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

Dalam kompilasi 32-bit dengan GCC (dan dengan MSVC 32- dan 64-bit), output dari program ini adalah:

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

Namun, program yang dihasilkan dari kompilasi GCC 64-bit akan menampilkan:

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

Ini aneh, karena long long intmerupakan integer 64-bit bertanda dan, untuk semua maksud dan tujuan, identik dengan tipe long intdan int64_t, jadi secara logis int64_t, long intdan long long intakan menjadi tipe yang setara - rakitan yang dihasilkan saat menggunakan tipe ini identik. Satu tampilan stdint.hmemberi tahu saya mengapa:

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

Dalam kompilasi 64-bit, int64_tis long int, bukan long long int(jelas).

Perbaikan untuk situasi ini cukup mudah:

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

Tapi ini sangat hackish dan tidak berskala dengan baik (fungsi substansi yang sebenarnya uint64_t, dll). Jadi pertanyaan saya adalah: Apakah ada cara untuk memberi tahu kompiler bahwa a long long intjuga a int64_t, sama seperti long intitu?


Pikiran awal saya adalah bahwa ini tidak mungkin, karena cara kerja definisi tipe C / C ++. Tidak ada cara untuk menentukan kesetaraan tipe dari tipe data dasar ke kompilator, karena itu adalah tugas kompiler (dan mengizinkannya dapat merusak banyak hal) dan typedefhanya berjalan satu arah.

Saya juga tidak terlalu peduli untuk mendapatkan jawaban di sini, karena ini adalah kasus tepi super-duper yang saya tidak curiga akan ada yang peduli ketika contoh tidak dibuat-buat (apakah itu berarti ini harus wiki komunitas?) .


Tambahkan : Alasan mengapa saya menggunakan spesialisasi template parsial daripada contoh yang lebih mudah seperti:

void go(int64_t) { }

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

adalah bahwa contoh tersebut akan tetap dikompilasi, karena long long intsecara implisit dapat diubah menjadi file int64_t.


Lampirkan : Satu-satunya jawaban sejauh ini mengasumsikan bahwa saya ingin tahu apakah suatu jenis adalah 64-bit. Saya tidak ingin menyesatkan orang agar berpikir bahwa saya peduli tentang itu dan mungkin seharusnya memberikan lebih banyak contoh di mana masalah ini terwujud.

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

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

Dalam contoh ini, some_type_trait<long int>akan menjadi boost::true_type, tetapi some_type_trait<long long int>tidak akan. Meskipun ini masuk akal dalam gagasan tipe C ++, ini tidak diinginkan.

Contoh lain adalah menggunakan qualifier seperti same_type(yang cukup umum digunakan dalam Konsep C ++ 0x):

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

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

Contoh itu gagal untuk dikompilasi, karena C ++ (dengan benar) melihat bahwa tipenya berbeda. g ++ akan gagal untuk dikompilasi dengan kesalahan seperti: tidak ada panggilan fungsi yang cocok same_type(long int&, long long int&).

Saya ingin menekankan bahwa saya memahami mengapa hal ini terjadi, tetapi saya mencari solusi yang tidak memaksa saya untuk mengulangi kode di semua tempat.

Travis Gockel
sumber
Karena penasaran, apakah program sampel Anda memberikan hasil yang sama untuk sizeofsetiap jenis? Mungkin kompilator memperlakukan ukuran secara long long intberbeda.
Blair Holloway
Sudahkah Anda mengkompilasi dengan C ++ 0x diaktifkan? C ++ 03 tidak memiliki <cstdint>, jadi mungkin fakta bahwa ia mengatakan "ini adalah ekstensi" (yang mana itu) adalah foobaring itu.
GManNickG
Ya, saya mungkin harus menentukan yang saya gunakan --std=c++0x. Dan ya sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8,.
Travis Gockel
1
Belum ada yang menyebutkan ini, tetapi seandainya terlewatkan: longdan long longmerupakan tipe yang berbeda (meskipun mereka memiliki ukuran & representasi yang sama). int64_tselalu merupakan alias untuk tipe lain yang sudah ada (terlepas dari namanya, typedeftidak membuat tipe baru, ini hanya memberi alias untuk yang sudah ada)
MM
3
Satu pernyataan penting hilang dari jawaban / komentar, yang membantu saya ketika keunikan ini menghantam saya: Jangan pernah menggunakan tipe ukuran tetap untuk templat yang andal dengan spesialisasi. Selalu gunakan tipe dasar dan tutupi semua kemungkinan kasus (bahkan jika Anda menggunakan tipe ukuran tetap untuk membuat contoh template tersebut). Semua kemungkinan kasus berarti: jika Anda perlu membuat contoh dengan int16_t, maka spesialisasikan dengan shortdan intdan Anda akan tercakup. (dan dengan signed charjika Anda merasa suka berpetualang)
Irfy

Jawaban:

49

Anda tidak perlu pergi ke 64-bit untuk melihat sesuatu seperti ini. Pertimbangkan int32_tplatform 32-bit umum. Ini mungkin typedef'ed sebagai intatau sebagai long, tapi jelas hanya satu dari dua pada satu waktu. intdan longtentu saja merupakan tipe yang berbeda.

Tidak sulit untuk melihat bahwa tidak ada solusi yang dibuat int == int32_t == longpada sistem 32-bit. Untuk alasan yang sama, tidak ada cara untuk membuat long == int64_t == long longsistem 64-bit.

Jika Anda bisa, kemungkinan konsekuensi akan agak menyakitkan untuk kode yang kelebihan beban foo(int), foo(long)dan foo(long long)- tiba-tiba mereka memiliki dua definisi untuk kelebihan yang sama ?!

Solusi yang benar adalah bahwa kode template Anda biasanya tidak bergantung pada jenis yang tepat, tetapi pada properti jenis tersebut. Seluruh same_typelogika masih bisa OK untuk kasus tertentu:

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

Yaitu, kelebihan beban foo(int64_t)tidak didefinisikan ketika itu persis sama dengan foo(long).

[sunting] Dengan C ++ 11, kami sekarang memiliki cara standar untuk menulis ini:

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

[sunting] Atau C ++ 20

long foo(long x);
int64_t foo(int64_t) requires (!std::is_same_v<int64_t, long>);
MSalters
sumber
1
Berita sedih, misalnya pada 64bit MSVC19 (2017) sizeof() longdan intidentik, tetapi std::is_same<long, int>::valuekembali false. Keanehan yang sama di AppleClang 9.1 di OSX HighSierra.
Ax3l
3
@ Ax3l: Itu tidak aneh. Hampir setiap kompilator sejak ISO C 90 memiliki setidaknya satu pasangan seperti itu.
MSalters
Itu benar, mereka adalah tipe yang berbeda.
Ax3l
6

Apakah Anda ingin tahu apakah suatu tipe memiliki tipe yang sama dengan int64_t atau Anda ingin tahu apakah ada 64 bit? Berdasarkan solusi yang Anda usulkan, saya pikir Anda bertanya tentang yang terakhir. Kalau begitu, saya akan melakukan sesuatu seperti

template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
Logan Capaldo
sumber
1
Apakah Anda tidak melewatkan satu returndan titik koma?
casablanca
1
Tetap saja, Anda harus menggunakan sizeofuntuk ini.
Ben Voigt
5
long long int dan long int bukanlah tipe yang sama apakah ukurannya sama atau tidak. Perilakunya tidak salah. Itu hanya C ++.
Logan Capaldo
5
Ini bukan batasan pengetikan nominal. Ini adalah batasan pengetikan nominal yang tidak berarti . Di masa lalu, standar de facto adalah short= 16 bit, long= 32 bit, dan int= ukuran asli. Di hari-hari 64-bit, intdan longtidak berarti apa - apa lagi.
dan04
1
@ dan04: Mereka tidak lebih atau kurang bermakna dari sebelumnya. shortadalah setidaknya 16 bit, intsetidaknya 16 bit, dan longsetidaknya 32 bit, dengan (notasi ceroboh berikut) pendek <= int <= lama. "Masa lalu" yang Anda maksud tidak pernah ada; selalu ada variasi dalam batasan yang diberlakukan oleh bahasa. The "All the world an x86" fallacy sama berbahayanya dengan "All the world a VAX fallacy" yang lebih tua.
Keith Thompson
1

Jadi pertanyaan saya adalah: Apakah ada cara untuk memberi tahu kompiler bahwa long int adalah juga int64_t, seperti int panjang?

Ini adalah pertanyaan atau masalah yang bagus, tapi saya rasa jawabannya TIDAK.

Juga, a long intmungkin bukan long long int.


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

Saya yakin ini libc. Saya rasa Anda ingin masuk lebih dalam.

Dalam kompilasi 32-bit dengan GCC (dan dengan MSVC 32- dan 64-bit), output dari program ini adalah:

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

Linux 32-bit menggunakan model data ILP32. Integer, long dan pointer adalah 32-bit. Tipe 64-bit adalah a long long.

Microsoft mendokumentasikan rentang di Rentang Tipe Data . Pepatah long longitu setara dengan __int64.

Namun, program yang dihasilkan dari kompilasi GCC 64-bit akan menampilkan:

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

Linux 64-bit menggunakan LP64model data. Panjangnya 64-bit dan long long64-bit. Seperti dengan 32-bit, Microsoft mendokumentasikan rentang pada Rentang Jenis Data dan masih panjang __int64.

Ada ILP64model data yang semuanya 64-bit. Anda harus melakukan pekerjaan ekstra untuk mendapatkan definisi untuk word32tipe Anda . Juga lihat makalah seperti Model Pemrograman 64-Bit: Mengapa LP64?


Tapi ini sangat hackish dan tidak berskala dengan baik (fungsi sebenarnya dari substansi, uint64_t, dll) ...

Ya, itu menjadi lebih baik. GCC mencampur dan mencocokkan deklarasi yang seharusnya menggunakan tipe 64 bit, sehingga mudah mendapat masalah meskipun Anda mengikuti model data tertentu. Misalnya, berikut ini menyebabkan kesalahan kompilasi dan memberitahu Anda untuk menggunakan -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);

Ini menghasilkan:

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

Jadi, abaikan LP64dan ubah menjadi:

typedef unsigned long long word64;

Kemudian, jelajahi gadget ARM IoT 64-bit yang mendefinisikan LP64dan menggunakan NEON:

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