long long int so với long int so với int64_t trong C ++


87

Tôi đã gặp phải một số hành vi kỳ lạ khi sử dụng các đặc điểm kiểu C ++ và đã thu hẹp vấn đề của mình xuống thành vấn đề nhỏ kỳ quặc này mà tôi sẽ đưa ra rất nhiều lời giải thích vì tôi không muốn để ngỏ bất kỳ điều gì để hiểu sai.

Giả sử bạn có một chương trình như vậy:

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

Trong cả biên dịch 32 bit với GCC (và với MSVC 32 và 64 bit), kết quả đầu ra của chương trình sẽ là:

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

Tuy nhiên, chương trình tạo ra từ biên dịch GCC 64-bit sẽ xuất ra:

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

Điều này thật kỳ lạ, vì đây long long intlà một số nguyên 64-bit có dấu và đối với mọi ý định và mục đích, giống hệt với kiểu long intint64_t, vì vậy về mặt logic int64_t, long intlong long intsẽ là kiểu tương đương - hợp ngữ được tạo ra khi sử dụng những kiểu này là giống hệt nhau. Một cái nhìn stdint.hcho tôi biết tại sao:

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

Trong một biên dịch 64-bit, int64_tlong int, không phải là một long long int(rõ ràng).

Cách khắc phục tình trạng này khá dễ dàng:

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

Nhưng điều này thật khủng khiếp và không mở rộng quy mô tốt (các chức năng thực tế của chất uint64_t, v.v.). Vì vậy, câu hỏi của tôi là: Có cách nào để nói với trình biên dịch rằng a long long intcũng là a int64_t, giống như long intlà không?


Suy nghĩ ban đầu của tôi là điều này là không thể, do cách định nghĩa kiểu C / C ++ hoạt động. Không có cách nào để chỉ định sự tương đương về kiểu của các kiểu dữ liệu cơ bản cho trình biên dịch, vì đó là công việc của trình biên dịch (và cho phép điều đó có thể phá vỡ nhiều thứ) và typedefchỉ đi theo một chiều.

Tôi cũng không quá lo lắng về việc nhận được câu trả lời ở đây, vì đây là một trường hợp siêu phức tạp mà tôi không nghi ngờ có ai đó sẽ quan tâm đến khi các ví dụ không quá khủng khiếp (điều đó có nghĩa là đây phải là wiki cộng đồng?) .


Nối : Lý do tại sao tôi đang sử dụng chuyên môn hóa từng phần mẫu thay vì ví dụ dễ dàng hơn như:

void go(int64_t) { }

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

là ví dụ đã nói đó sẽ vẫn biên dịch, vì long long inthoàn toàn có thể chuyển đổi thành an int64_t.


Nối : Câu trả lời duy nhất cho đến nay giả định rằng tôi muốn biết một loại có phải là 64-bit hay không. Tôi không muốn đánh lừa mọi người nghĩ rằng tôi quan tâm đến điều đó và có lẽ nên cung cấp thêm các ví dụ về nơi biểu hiện của vấn đề này.

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

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

Trong ví dụ này, some_type_trait<long int>sẽ là a boost::true_type, nhưng some_type_trait<long long int>sẽ không phải. Mặc dù điều này có ý nghĩa trong ý tưởng của C ++ về các loại, nhưng nó không được mong muốn.

Một ví dụ khác là sử dụng một định tính như same_type(khá phổ biến để sử dụng trong C ++ 0x Khái niệm):

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

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

Ví dụ đó không thể biên dịch, vì C ++ (chính xác) thấy rằng các kiểu khác nhau. g ++ sẽ không biên dịch được với lỗi như: không có lệnh gọi hàm phù hợp same_type(long int&, long long int&).

Tôi muốn nhấn mạnh rằng tôi hiểu tại sao điều này lại xảy ra, nhưng tôi đang tìm kiếm một giải pháp thay thế không buộc tôi phải lặp lại mã ở khắp nơi.


Vì tò mò, chương trình mẫu của bạn có đưa ra kết quả giống nhau cho sizeoftừng loại không? Có lẽ trình biên dịch đang xử lý kích thước long long intkhác nhau.
Blair Holloway

Bạn đã bật C ++ 0x chưa? C ++ 03 không có <cstdint>, vì vậy có lẽ thực tế là nó phải nói "đây là một phần mở rộng" (nó là) đang phát triển nó.
GManNickG

Có, tôi có lẽ nên chỉ định rằng tôi đang sử dụng --std=c++0x. Và có sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8,.
Travis Gockel

1
Không ai đề cập đến điều này, nhưng trong trường hợp nó bị bỏ qua: longlong longlà các loại riêng biệt (ngay cả khi chúng có cùng kích thước và đại diện). int64_tluôn là bí danh cho một loại hiện có khác (mặc dù tên của nó, typedefkhông tạo ra loại mới, nó chỉ đặt bí danh cho một loại đã tồn tại)
MM

3
Một tuyên bố quan trọng bị thiếu trong các câu trả lời / nhận xét, điều này đã giúp ích cho tôi khi điều kỳ lạ này ập đến với tôi: Không bao giờ sử dụng các loại kích thước cố định cho các mẫu chuyên dụng đáng tin cậy. Luôn sử dụng các loại cơ bản và bao gồm tất cả các trường hợp có thể xảy ra (ngay cả khi bạn sử dụng các loại kích thước cố định để tạo các mẫu đó). Tất cả các trường hợp có thể có nghĩa là: nếu bạn cần phải nhanh chóng với int16_t, sau đó chuyên với shortintvà bạn sẽ được bảo hiểm. (và với signed charnếu bạn cảm thấy mạo hiểm)
Irfy

Câu trả lời:


49

Bạn không cần phải truy cập 64-bit để xem một cái gì đó như thế này. Xem xét int32_ttrên các nền tảng 32-bit phổ biến. Nó có thể là typedef'ed as inthoặc as a long, nhưng rõ ràng là chỉ một trong hai thứ tại một thời điểm. intlongtất nhiên là các loại riêng biệt.

Không khó để thấy rằng không có cách giải quyết nào được áp dụng int == int32_t == longtrên các hệ thống 32 bit. Vì lý do tương tự, không có cách nào để thực hiện long == int64_t == long longtrên hệ thống 64-bit.

Nếu bạn có thể, hậu quả có thể xảy ra sẽ khá đau đớn đối với mã quá tải foo(int), foo(long)foo(long long)- đột nhiên chúng có hai định nghĩa cho cùng một tình trạng quá tải ?!

Giải pháp chính xác là mã mẫu của bạn thường không nên dựa vào một loại chính xác, mà dựa trên các thuộc tính của loại đó. Toàn bộ same_typelogic vẫn có thể ổn cho các trường hợp cụ thể:

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

Tức là, quá tải foo(int64_t)không được xác định khi nó chính xác giống như foo(long).

[sửa] Với C ++ 11, bây giờ chúng ta có một cách chuẩn để viết điều này:

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

[sửa] Hoặc C ++ 20

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

1
Tin buồn là, ví dụ như trên 64bit MSVC19 (2017) sizeof() longintgiống hệt nhau, nhưng std::is_same<long, int>::valuetrả về false. Điều kỳ lạ tương tự trên AppleClang 9.1 trên OSX HighSierra.
Ax3l

3
@ Ax3l: Không lạ đâu. Hầu như mọi trình biên dịch kể từ ISO C 90 đều có ít nhất một cặp như vậy.
MSalters

Đó là sự thật, chúng là những loại riêng biệt.
Ax3l

6

Bạn có muốn biết nếu một kiểu có cùng kiểu với int64_t hay không hay bạn muốn biết một thứ có phải là 64 bit không? Dựa trên giải pháp được đề xuất của bạn, tôi nghĩ bạn đang hỏi về giải pháp thứ hai. Trong trường hợp đó, tôi sẽ làm điều gì đó như

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

1
Bạn không thiếu một returnvà một dấu chấm phẩy?
casablanca

1
Tuy nhiên, bạn nên sử dụng sizeofcho việc này.
Ben Voigt

5
long long int và long int không cùng kiểu cho dù chúng có cùng kích thước hay không. Hành vi không có sai sót. Đó chỉ là C ++.
Logan Capaldo

5
Nó không phải là một giới hạn của đánh máy danh nghĩa. Đó là một hạn chế của việc nhập danh nghĩa vô nghĩa. Ngày xưa, tiêu chuẩn trên thực tế là short= 16 bit, long= 32 bit và int= kích thước gốc. Trong những ngày này của 64-bit, intlongkhông còn ý nghĩa gì nữa.
dan04,

1
@ dan04: Chúng không hơn không kém ý nghĩa hơn bao giờ hết. shortít nhất 16 bit, intlà ít nhất 16 bit và longít nhất là 32 bit, với (ký hiệu nghiêng sau) ngắn <= int <= long. “Ngày xưa” mà bạn ám chỉ chưa từng tồn tại; luôn có những biến thể trong các hạn chế do ngôn ngữ áp đặt. "Tất cả thế giới là một x86" sai lầm chỉ là nguy hiểm như cũ "Tất cả thế giới là một sai lầm VAX.
Keith Thompson

1

Vì vậy, câu hỏi của tôi là: Có cách nào để nói với trình biên dịch rằng một int dài dài cũng là một int64_t, giống như int dài không?

Đây là một câu hỏi hay một vấn đề, nhưng tôi nghi ngờ câu trả lời là KHÔNG.

Ngoài ra, a long intcó thể không phải là a long long int.


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

Tôi tin rằng đây là libc. Tôi nghi ngờ bạn muốn đi sâu hơn.

Trong cả biên dịch 32 bit với GCC (và với MSVC 32 và 64 bit), kết quả đầu ra của chương trình sẽ là:

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

Linux 32-bit sử dụng mô hình dữ liệu ILP32. Số nguyên, độ dài và con trỏ là 32-bit. Loại 64-bit là a long long.

Microsoft ghi lại các phạm vi tại Dải loại dữ liệu . Câu nói long longtương đương với __int64.

Tuy nhiên, chương trình tạo ra từ biên dịch GCC 64-bit sẽ xuất ra:

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

Linux 64-bit sử dụng LP64mô hình dữ liệu. Độ dài là 64-bit và long long64-bit. Như với 32-bit, Microsoft ghi lại các phạm vi tại Dải loại dữ liệu và dài vẫn là __int64.

Có một ILP64mô hình dữ liệu mà mọi thứ đều là 64-bit. Bạn phải làm thêm một số công việc để có được định nghĩa cho word32loại của bạn . Cũng xem các bài báo như Mô hình lập trình 64-Bit: Tại sao lại là LP64?


Nhưng điều này thật khủng khiếp và không mở rộng quy mô tốt (các chức năng thực tế của chất, uint64_t, v.v.) ...

Vâng, nó thậm chí còn tốt hơn. GCC trộn và khớp các khai báo được cho là sử dụng loại 64 bit, do đó, nó dễ gặp rắc rối ngay cả khi bạn tuân theo một mô hình dữ liệu cụ thể. Ví dụ: nguyên nhân sau gây ra lỗi biên dịch và yêu cầu bạn sử dụng -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);

Nó dẫn đến:

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

Vì vậy, hãy bỏ qua LP64và thay đổi nó thành:

typedef unsigned long long word64;

Sau đó, chuyển sang tiện ích ARM IoT 64 bit xác định LP64và sử dụng NEON:

error: invalid conversion from `word64* {aka long long unsigned int*}' to `uint64_t*'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.