long long int เทียบกับ long int เทียบกับ int64_t ใน C ++


88

ฉันพบพฤติกรรมแปลก ๆ ในขณะที่ใช้ลักษณะประเภท C ++ และได้ จำกัด ปัญหาของฉันให้แคบลงเหลือเพียงปัญหาเล็ก ๆ น้อย ๆ ที่เล่นโวหารซึ่งฉันจะให้คำอธิบายมากมายเนื่องจากฉันไม่ต้องการปล่อยให้มีการตีความที่ผิด

สมมติว่าคุณมีโปรแกรมดังนี้:

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

ในการคอมไพล์ 32 บิตด้วย GCC (และ MSVC 32 และ 64 บิต) ผลลัพธ์ของโปรแกรมจะเป็น:

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

อย่างไรก็ตามโปรแกรมที่เกิดจากการคอมไพล์ GCC 64 บิตจะแสดงผลลัพธ์:

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

นี่คืออยากรู้อยากเห็นตั้งแต่long long intเป็นลงนามจำนวนเต็ม 64 บิตและเป็นสำหรับ intents และวัตถุประสงค์เหมือนกับที่long intและint64_tประเภทดังนั้นเหตุผลint64_t, long intและlong long intจะเป็นประเภทเทียบเท่า - สร้างการชุมนุมเมื่อใช้ประเภทนี้เป็นเหมือนกัน ดูหนึ่งครั้งstdint.hบอกฉันว่าทำไม:

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

ในการคอมไพล์ 64 บิตint64_tคือlong intไม่ใช่ a long long int(ชัด ๆ )

การแก้ไขสถานการณ์นี้ค่อนข้างง่าย:

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

แต่นี่เป็นการแฮ็กอย่างน่ากลัวและปรับขนาดได้ไม่ดี (หน้าที่จริงของสสารuint64_tฯลฯ ) คำถามของฉันคือ:มีวิธีบอกคอมไพเลอร์ไหมว่า a long long intคือ a int64_tเช่นเดียวกับlong intis?


ความคิดเริ่มต้นของฉันคือมันเป็นไปไม่ได้เนื่องจากวิธีการทำงานของคำจำกัดความประเภท C / C ++ ไม่มีวิธีระบุประเภทความเท่าเทียมกันของชนิดข้อมูลพื้นฐานให้กับคอมไพเลอร์เนื่องจากนั่นคืองานของคอมไพเลอร์ (และการอนุญาตให้ทำสิ่งต่างๆมากมาย) และtypedefไปได้เพียงวิธีเดียว

ฉันไม่กังวลกับการได้รับคำตอบที่นี่มากนักเนื่องจากนี่เป็นกรณีสุดยอดที่ไม่คาดคิดว่าจะมีใครสนใจเมื่อตัวอย่างไม่ได้ถูกสร้างขึ้นอย่างน่ากลัว (นั่นหมายความว่านี่ควรเป็นวิกิชุมชนหรือไม่) .


ต่อท้าย : เหตุผลที่ฉันใช้ความเชี่ยวชาญพิเศษของเทมเพลตบางส่วนแทนที่จะเป็นตัวอย่างที่ง่ายกว่าเช่น:

void go(int64_t) { }

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

ตัวอย่างดังกล่าวจะยังคงคอมไพล์long long intอยู่เนื่องจากโดยปริยายแปลงเป็นint64_tไฟล์.


ต่อท้าย : คำตอบเดียวที่สรุปได้ว่าฉันต้องการทราบว่าประเภทเป็น 64 บิตหรือไม่ ฉันไม่อยากทำให้คนเข้าใจผิดคิดว่าฉันสนใจเรื่องนั้นและน่าจะให้ตัวอย่างเพิ่มเติมว่าปัญหานี้ปรากฏตัวที่ใด

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

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

ในตัวอย่างนี้some_type_trait<long int>จะเป็น a boost::true_typeแต่some_type_trait<long long int>จะไม่เป็น แม้ว่าสิ่งนี้จะสมเหตุสมผลในความคิดของ C ++ แต่ก็ไม่เป็นที่ต้องการ

อีกตัวอย่างหนึ่งคือการใช้ qualifier เช่นsame_type(ซึ่งเป็นเรื่องธรรมดาที่จะใช้ใน C ++ 0x Concepts):

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

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

ตัวอย่างนั้นไม่สามารถคอมไพล์ได้เนื่องจาก C ++ (อย่างถูกต้อง) เห็นว่าประเภทต่างกัน กรัม ++ จะล้มเหลวในการรวบรวมกับข้อผิดพลาดที่ชอบ: same_type(long int&, long long int&)ไม่มีการเรียกใช้ฟังก์ชันการจับคู่

ฉันอยากจะเน้นว่าฉันเข้าใจว่าเหตุใดจึงเกิดเหตุการณ์นี้ขึ้น แต่ฉันกำลังมองหาวิธีแก้ปัญหาที่ไม่บังคับให้ฉันต้องทำรหัสซ้ำในทุกที่


ด้วยความอยากรู้อยากเห็นโปรแกรมตัวอย่างของคุณให้ผลลัพธ์ที่เหมือนกันสำหรับsizeofแต่ละประเภทหรือไม่ บางทีคอมไพเลอร์กำลังปฏิบัติกับขนาดที่long long intแตกต่างกัน
Blair Holloway

คุณได้รวบรวมโดยเปิดใช้งาน C ++ 0x หรือไม่? C ++ 03 ไม่มี<cstdint>ดังนั้นอาจจะมีการบอกว่า "นี่คือส่วนขยาย" (ซึ่งก็คือ) มันทำให้เกิดฟอง
GManNickG

--std=c++0xใช่ฉันควรจะได้อาจจะระบุว่าฉันใช้ และใช่sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8.
Travis Gockel

1
ยังไม่มีใครพูดถึงเรื่องนี้ แต่ในกรณีที่ถูกมองข้าม: longและlong longเป็นประเภทที่แตกต่างกัน (แม้ว่าจะมีขนาดและการนำเสนอเท่ากันก็ตาม) int64_tเป็นนามแฝงสำหรับประเภทอื่นที่มีอยู่เสมอ (แม้จะมีชื่อ แต่typedefไม่ได้สร้างประเภทใหม่ แต่เพียงแค่ให้นามแฝงกับประเภทที่มีอยู่แล้ว)
MM

3
ข้อความสำคัญอย่างหนึ่งขาดหายไปจากคำตอบ / ความคิดเห็นซึ่งช่วยฉันเมื่อมุมแหลมนี้กระทบฉัน: อย่าใช้ประเภทขนาดคงที่สำหรับเทมเพลตที่เชี่ยวชาญอย่างน่าเชื่อถือ ใช้ประเภทพื้นฐานเสมอและครอบคลุมกรณีที่เป็นไปได้ทั้งหมด (แม้ว่าคุณจะใช้ประเภทขนาดคงที่เพื่อสร้างอินสแตนซ์เทมเพลตเหล่านั้น) ทุกกรณีที่เป็นไปได้หมายความว่าถ้าคุณจำเป็นต้องยกตัวอย่างด้วยint16_tแล้วมีความเชี่ยวชาญด้วยshortและintและคุณจะได้รับการคุ้มครอง (และsigned charถ้าคุณรู้สึกอยากผจญภัย)
Irfy

คำตอบ:


49

คุณไม่จำเป็นต้องไปที่ 64 บิตเพื่อดูสิ่งนี้ พิจารณาint32_tบนแพลตฟอร์ม 32 บิตทั่วไป มันอาจจะเป็นtypedef'ed as intหรือ as a longแต่เห็นได้ชัดว่ามีเพียงหนึ่งในสองครั้งเท่านั้น intและlongเป็นประเภทที่แตกต่างกัน

ไม่ยากที่จะเห็นว่าไม่มีวิธีแก้ปัญหาใด ๆ ที่ทำint == int32_t == longกับระบบ 32 บิต ด้วยเหตุผลเดียวกันไม่มีวิธีใดที่จะสร้างlong == int64_t == long longบนระบบ 64 บิต

หากคุณสามารถทำได้, ผลกระทบที่เป็นไปได้จะค่อนข้างเจ็บปวดสำหรับรหัสที่มากเกินไปfoo(int), foo(long)และfoo(long long)- พวกเขาก็จะมีสองคำจำกัดความของเกินพิกัดเดียวกัน ?!

วิธีแก้ไขที่ถูกต้องคือโดยปกติแล้วโค้ดเทมเพลตของคุณไม่ควรขึ้นอยู่กับประเภทที่แน่นอน แต่ขึ้นอยู่กับคุณสมบัติของประเภทนั้น ๆ same_typeตรรกะทั้งหมดยังคงใช้ได้สำหรับบางกรณี:

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

คือการโอเวอร์โหลดfoo(int64_t)ไม่ได้ถูกกำหนดเมื่อมันตรงfoo(long)เช่นเดียวกับ

[แก้ไข] ด้วย C ++ 11 ตอนนี้เรามีวิธีมาตรฐานในการเขียนสิ่งนี้:

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

[แก้ไข] หรือ C ++ 20

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

1
ข่าวเศร้าเป็นเช่นบน 64bit MSVC19 (2017) sizeof() longและintเป็นเหมือนกัน แต่ผลตอบแทนstd::is_same<long, int>::value falseความแปลกเหมือนกันบน AppleClang 9.1 บน OSX HighSierra
Ax3l

3
@ Ax3l: นั่นไม่แปลก แทบทุกคอมไพเลอร์ตั้งแต่ ISO C 90 มีคู่ดังกล่าวอย่างน้อยหนึ่งคู่
MSalters

นั่นเป็นความจริงประเภทที่แตกต่างกัน
Ax3l

6

คุณต้องการทราบว่าประเภทเป็นประเภทเดียวกับ int64_t หรือคุณต้องการทราบว่ามี 64 บิตหรือไม่? จากวิธีแก้ปัญหาที่คุณเสนอฉันคิดว่าคุณกำลังถามถึงเรื่องหลัง ในกรณีนั้นฉันจะทำบางอย่างเช่น

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

1
คุณไม่มีreturnเครื่องหมายอัฒภาคหรือไม่?
casablanca

1
ถึงกระนั้นคุณควรใช้sizeofสำหรับสิ่งนี้
Ben Voigt

5
long long int และ long int ไม่ใช่ประเภทเดียวกันไม่ว่าจะมีขนาดเท่ากันหรือไม่ พฤติกรรมไม่ผิดพลาด นั่นเป็นเพียง C ++
Logan Capaldo

5
ไม่ใช่ข้อ จำกัด ของการพิมพ์เล็กน้อย เป็นข้อ จำกัด ของการพิมพ์เล็กน้อยที่ไม่มีความหมาย ในสมัยก่อนมาตรฐานโดยพฤตินัยคือshort= 16 บิตlong= 32 บิตและint= ขนาดดั้งเดิม ในวันนี้ของ 64 บิตintและlongไม่มีความหมายอะไรอีกต่อไป
dan04

1
@ dan04: มันไม่มีความหมายมากหรือน้อยไปกว่าที่เคยเป็นมา shortเป็นอย่างน้อย 16 บิตintเป็นอย่างน้อย 16 บิตและlongอย่างน้อย 32 บิตโดยมี (สัญกรณ์เลอะเทอะ) สั้น <= int <= long "สมัยก่อน" ที่คุณอ้างถึงไม่เคยมีอยู่จริง มีการเปลี่ยนแปลงอยู่เสมอภายในข้อ จำกัด ที่กำหนดโดยภาษา ความผิดพลาด "All the world an x86" นั้นอันตรายพอ ๆ กับรุ่นเก่า "All the world a VAX fallacy.
Keith Thompson

1

คำถามของฉันคือ: มีวิธีบอกคอมไพเลอร์ไหมว่า long long int คือ int64_t เช่นเดียวกับ long int

นี่เป็นคำถามหรือปัญหาที่ดี แต่ฉันสงสัยว่าคำตอบคือไม่

นอกจากนี้ a long intอาจไม่ใช่ไฟล์long long int.


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

ฉันเชื่อว่านี่คือ libc ฉันสงสัยว่าคุณต้องการลงลึกกว่านี้

ในการคอมไพล์ 32 บิตด้วย GCC (และ MSVC 32 และ 64 บิต) ผลลัพธ์ของโปรแกรมจะเป็น:

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

ลินุกซ์ 32 บิตใช้โมเดลข้อมูล ILP32 จำนวนเต็ม longs และ pointers เป็น 32 บิต ประเภท 64 บิตคือไฟล์long long.

เอกสาร Microsoft ช่วงที่ชนิดของข้อมูลช่วง กล่าวว่าlong longเทียบเท่ากับ__int64.

อย่างไรก็ตามโปรแกรมที่เกิดจากการคอมไพล์ GCC 64 บิตจะแสดงผลลัพธ์:

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

ลินุกซ์ 64 บิตใช้LP64โมเดลข้อมูล Longs คือ 64 บิตและlong long64 บิต เช่นเดียวกับ 32 บิต Microsoft จะบันทึกช่วงที่Data Type Rangesและ long long ยังคง__int64อยู่

มีILP64โมเดลข้อมูลที่ทุกอย่างเป็น 64 บิต คุณต้องทำงานพิเศษเพื่อให้ได้คำจำกัดความสำหรับword32ประเภทของคุณ ดูเอกสารเช่นรุ่นการเขียนโปรแกรม 64 บิต: ทำไมต้องเป็น LP64


แต่นี่เป็นการแฮ็กอย่างน่ากลัวและปรับขนาดได้ไม่ดี (หน้าที่จริงของสสาร, uint64_t ฯลฯ ) ...

ใช่มันจะดียิ่งขึ้น GCC ผสมผสานและจับคู่การประกาศที่ควรใช้ประเภท 64 บิตดังนั้นจึงเป็นเรื่องง่ายที่จะประสบปัญหาแม้ว่าคุณจะทำตามแบบจำลองข้อมูลใดก็ตาม ตัวอย่างเช่นต่อไปนี้ทำให้เกิดข้อผิดพลาดในการคอมไพล์และบอกให้คุณใช้-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);

ส่งผลให้:

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

ดังนั้นอย่าสนใจLP64และเปลี่ยนเป็น:

typedef unsigned long long word64;

จากนั้นเดินไปที่แกดเจ็ต ARM IoT 64 บิตที่กำหนดLP64และใช้ NEON:

error: invalid conversion from `word64* {aka long long unsigned int*}' to `uint64_t*'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.