ทำไมเป็น 0 <-0x80000000


253

ฉันมีโปรแกรมง่ายๆด้านล่าง:

#include <stdio.h>

#define INT32_MIN        (-0x80000000)

int main(void) 
{
    long long bal = 0;

    if(bal < INT32_MIN )
    {
        printf("Failed!!!");
    }
    else
    {
        printf("Success!!!");
    }
    return 0;
}

เงื่อนไขif(bal < INT32_MIN )เป็นจริงเสมอ มันเป็นไปได้ยังไงกัน?

มันทำงานได้ดีถ้าฉันเปลี่ยนแมโครเป็น:

#define INT32_MIN        (-2147483648L)

ใครสามารถชี้ให้เห็นปัญหา


3
เท่าไหร่CHAR_BIT * sizeof(int)?
5gon12eder

1
คุณได้ลองพิมพ์บอลบาล?
Ryan Fitzpatrick

10
IMHO สิ่งที่น่าสนใจกว่านั้นก็คือมันเป็นความจริงเท่านั้นสำหรับ-0x80000000แต่เท็จ-0x80000000L, -2147483648และ-2147483648L(GCC 4.1.2) ดังนั้นคำถามคือทำไมเป็น int ตัวอักษร -0x80000000ที่แตกต่างจากตัวอักษรนั้น int -2147483648?
Andreas Fester

2
@Bathsheba ฉันเพิ่งเรียกใช้โปรแกรมในคอมไพเลอร์ออนไลน์tutorialspoint.com/codingground.htm
Jayesh Bhoi

2
หากคุณเคยสังเกตว่า (บางสาขาของ) <limits.h>กำหนดINT_MINเป็น(-2147483647 - 1)ตอนนี้คุณรู้ว่าทำไม
zwol

คำตอบ:


363

มันค่อนข้างบอบบาง

ตัวอักษรจำนวนเต็มทุกตัวในโปรแกรมของคุณมีชนิด ประเภทใดที่มีการควบคุมโดยตารางใน 6.4.4.1:

Suffix      Decimal Constant    Octal or Hexadecimal Constant

none        int                 int
            long int            unsigned int
            long long int       long int
                                unsigned long int
                                long long int
                                unsigned long long int

หากตัวเลขตามตัวอักษรไม่สามารถอยู่ในintประเภทเริ่มต้นมันจะพยายามพิมพ์ขนาดใหญ่ต่อไปตามที่ระบุในตารางข้างต้น ดังนั้นสำหรับตัวอักษรจำนวนเต็มทศนิยมปกติมันจะเป็นเช่น:

  • ลอง int
  • หากไม่พอดีให้ลอง long
  • long longหากไม่สามารถพอดีลอง

ตัวอักษรฐานสิบหกจะทำงานแตกต่างกันออกไป! หากตัวอักษรไม่พอดีกับประเภทที่มีลายเซ็นintมันจะลองunsigned intก่อนที่จะลองประเภทที่มีขนาดใหญ่กว่า ดูความแตกต่างในตารางด้านบน

ดังนั้นในระบบ 32 บิตที่แท้จริงของคุณเป็นประเภท0x80000000unsigned int

ซึ่งหมายความว่าคุณสามารถใช้-โอเปอเรเตอร์unary กับตัวอักษรโดยไม่ต้องเรียกใช้พฤติกรรมที่กำหนดโดยการนำไปใช้เช่นเดียวกับที่คุณทำเมื่อล้นจำนวนเต็มที่ลงนาม แต่คุณจะได้รับค่า0x80000000เป็นค่าบวก

bal < INT32_MINเรียกการแปลงทางคณิตศาสตร์ตามปกติและผลของการแสดงออกที่0x80000000ได้รับการสนับสนุนจากการunsigned int long longค่า0x80000000ถูกเก็บรักษาไว้และ 0 น้อยกว่า 0x80000000 ดังนั้นผลลัพธ์

เมื่อคุณแทนที่ตัวอักษรที่มี2147483648Lคุณใช้สัญกรณ์ทศนิยมและดังนั้นจึงคอมไพเลอร์ไม่ได้รับแต่พยายามที่จะพอดีภายในunsigned int longนอกจากนี้ยังมีคำต่อท้าย L บอกว่าคุณต้องการถ้าเป็นไปได้long คำต่อท้าย L จริง ๆ แล้วมีกฎที่คล้ายกันถ้าคุณอ่านตารางที่กล่าวถึงใน 6.4.4.1 ต่อไป: ถ้าจำนวนไม่พอดีกับที่ร้องขอlongซึ่งมันไม่ได้อยู่ในตัวอักษร 32 บิตคอมไพเลอร์จะให้long longตำแหน่งที่คุณอยู่ จะพอดี


3
"... แทนที่ตัวอักษรด้วย -2147483648L คุณจะได้รับความยาวอย่างชัดเจนซึ่งลงชื่อ" อืมใน 32 บิตlongระบบ2147483648Lจะไม่พอดีกับlongดังนั้นมันจะกลายเป็นlong long, แล้ว-ถูกนำไปใช้ - หรือดังนั้นฉันคิดว่า
chux - Reinstate Monica

2
@ASH เพราะจำนวนสูงสุดเป็น int 0x7FFFFFFFสามารถมีแล้ว ลองด้วยตัวคุณเอง:#include <limits.h> printf("%X\n", INT_MAX);
Lundin

5
@ASH อย่าสับสนระหว่างการแทนฐานสิบหกของตัวอักษรจำนวนเต็มในซอร์สโค้ดด้วยการแทนค่าไบนารี่พื้นฐานของหมายเลขที่ลงชื่อ ตัวอักษร0x7FFFFFFFเมื่อเขียนในซอร์สโค้ดจะเป็นจำนวนบวกเสมอ แต่intตัวแปรของคุณสามารถมีเลขฐานสองแบบดิบได้ถึงค่า 0xFFFFFFFF
Lundin

2
@ASH ìnt n = 0x80000000บังคับให้แปลงจากตัวอักษรที่ไม่ได้ลงชื่อไปเป็นประเภทที่เซ็นชื่อ สิ่งที่จะเกิดขึ้นนั้นขึ้นอยู่กับคอมไพเลอร์ของคุณซึ่งเป็นพฤติกรรมที่กำหนดโดยการนำไปปฏิบัติ ในกรณีนี้มันเลือกที่จะแสดงตัวอักษรทั้งหมดลงในint, เขียนทับเครื่องหมายบิต ในระบบอื่นคุณอาจไม่สามารถแสดงประเภทและคุณเรียกใช้การทำงานที่ไม่ได้กำหนดโปรแกรมอาจหยุดทำงาน คุณจะได้รับพฤติกรรมที่เหมือนกันถ้าคุณทำเช่นint n=2147483648;นั้นมันไม่เกี่ยวข้องกับสัญกรณ์ฐานสิบหกเลย
Lundin

3
คำอธิบายของวิธีการที่ unary -ถูกนำไปใช้กับจำนวนเต็มที่ไม่ได้ลงนามสามารถขยายได้เล็กน้อย ฉันสันนิษฐานเสมอ (แม้ว่าโชคดีไม่เคยพึ่งพาสมมติฐาน) ว่าค่าที่ไม่ได้ลงชื่อจะ "เลื่อน" เป็นค่าที่เซ็นชื่อหรืออาจเป็นไปได้ว่าผลลัพธ์จะไม่ได้กำหนด (จริงๆแล้วมันควรจะเป็นข้อผิดพลาดในการคอมไพล์; - 3uแม้แต่มีความหมายว่าอะไร)
Kyle Strand

27

0x80000000เป็นunsignedตัวอักษรที่มีค่า 2147483648

การใช้ unary ลบกับสิ่งนี้ยังให้ประเภทที่ไม่ได้ลงชื่อกับค่าที่ไม่เป็นศูนย์ (อันที่จริงแล้วสำหรับค่าที่ไม่เป็นศูนย์ค่าxที่คุณจะได้คือUINT_MAX - x + 1)


23

ตัวอักษรจำนวนเต็มนี้มีประเภท0x80000000unsigned int

ตามมาตรฐาน C (6.4.4.1 ค่าคงที่จำนวนเต็ม)

5 ประเภทของค่าคงที่จำนวนเต็มเป็นรายการแรกของรายการที่สอดคล้องกันซึ่งสามารถแสดงมูลค่าได้

unsigned intและคงจำนวนเต็มนี้สามารถแสดงโดยแบ่งตามชนิดของ

ดังนั้นการแสดงออกนี้

-0x80000000มีunsigned intประเภทเดียวกัน ยิ่งไปกว่านั้นมันมีค่าเหมือนกัน 0x80000000ในการแสดงส่วนประกอบสองอย่างที่คำนวณด้วยวิธีต่อไปนี้

-0x80000000 = ~0x80000000 + 1 => 0x7FFFFFFF + 1 => 0x80000000

นี่เป็นผลข้างเคียงหากเขียนเช่น

int x = INT_MIN;
x = abs( x );

INT_MINผลจะออกมาอีกครั้ง

ดังนั้นในสภาพนี้

bal < INT32_MIN

มีการเปรียบเทียบ0กับค่าที่ไม่ได้ลงชื่อ0x80000000แปลงเป็น long long int ตามกฎของการแปลงเลขคณิตปกติ

จะเห็นว่า 0 0x80000000น้อยกว่า


12

คงเป็นตัวเลขเป็นประเภท0x80000000 unsigned intถ้าเราใช้-0x80000000และทำเลข 2 ชมเชยเราจะได้:

~0x80000000 = 0x7FFFFFFF
0x7FFFFFFF + 1 = 0x80000000

-0x80000000 == 0x80000000ดังนั้น และการเปรียบเทียบ(0 < 0x80000000)(เนื่องจาก0x80000000ไม่ได้ลงชื่อ) เป็นความจริง


นี้ซึม 32 บิตints แม้ว่าจะเป็นตัวเลือกที่ใช้กันทั่วไป แต่ในการติดตั้งที่กำหนดintอาจจะแคบหรือกว้างกว่าก็ได้ อย่างไรก็ตามเป็นการวิเคราะห์ที่ถูกต้องสำหรับกรณีนั้น
John Bollinger

สิ่งนี้ไม่เกี่ยวข้องกับรหัสของ OP -0x80000000เป็นเลขคณิตที่ไม่ได้ลงชื่อ ~0x800000000เป็นรหัสที่แตกต่างกัน
MM

นี่ดูเหมือนจะเป็นคำตอบที่ดีที่สุดและถูกต้องสำหรับฉัน @MM เขาอธิบายวิธีการใช้ส่วนประกอบสองอย่าง คำตอบนี้ระบุถึงสิ่งที่เครื่องหมายลบกำลังทำกับหมายเลขนั้นโดยเฉพาะ
Octopus

@ Octopus เครื่องหมายลบไม่ได้ใช้ส่วนเติมเต็มของ 2 กับหมายเลข (!) แม้ว่ามันจะชัดเจน แต่ก็ไม่ได้อธิบายว่าเกิดอะไรขึ้นในโค้ด-0x80000000! ในความเป็นจริง 2 ส่วนประกอบของคำถามนี้ไม่เกี่ยวข้องทั้งหมด
MM

12

มีความสับสนเกิดขึ้นเมื่อคิดว่า-ส่วนนั้นเป็นค่าคงที่ตัวเลข

ในรหัสด้านล่าง0x80000000เป็นค่าคงที่ตัวเลข ประเภทของมันจะถูกกำหนดเฉพาะในที่ -ถูกนำไปใช้ในภายหลังและไม่เปลี่ยนประเภท

#define INT32_MIN        (-0x80000000)
long long bal = 0;
if (bal < INT32_MIN )

ค่าคงที่ตัวเลขที่ไม่มีการตกแต่งดิบเป็นค่าบวก

intถ้ามันเป็นทศนิยมแล้วประเภทที่ได้รับมอบหมายเป็นชนิดแรกที่จะถือไว้: long, long long,

intหากคงเป็นฐานแปดเลขฐานสิบหกหรือจะได้รับชนิดแรกที่ถือมัน: unsigned, long, unsigned long, long long, unsigned long long,

0x80000000ในระบบของ OP ได้รับชนิดของหรือunsigned unsigned longไม่ว่าจะด้วยวิธีใดก็เป็นประเภทที่ไม่ได้ลงนาม

-0x80000000นอกจากนี้ยังมีค่าที่ไม่เป็นศูนย์และเป็นประเภทที่ไม่ได้ลงนามซึ่งมีค่ามากกว่า 0 เมื่อโค้ดเปรียบเทียบกับ a long long, ค่าจะไม่เปลี่ยนแปลงใน 2 ด้านของการเปรียบเทียบดังนั้นจึง0 < INT32_MINเป็นจริง


คำจำกัดความอื่นหลีกเลี่ยงพฤติกรรมที่อยากรู้อยากเห็นนี้

#define INT32_MIN        (-2147483647 - 1)

ให้เราเดินไปในดินแดนแฟนตาซีในขณะที่intและunsigned48- บิต

จากนั้น0x80000000เหมาะกับในและเพื่อให้เป็นประเภท int เป็นจำนวนลบและผลลัพธ์ของการพิมพ์แตกต่างกันint-0x80000000

[กลับสู่คำพูดจริง]

เนื่องจาก0x80000000เหมาะสมกับประเภทที่ไม่ได้ลงนามก่อนประเภทที่เซ็นชื่อเนื่องจากมีขนาดใหญ่กว่าsome_signed_MAXแต่ยังอยู่ในsome_unsigned_MAXประเภทที่ไม่ได้ลงชื่อ


8

C มีกฎที่แท้จริงจำนวนเต็มอาจจะเป็นsignedหรือunsignedขึ้นอยู่กับว่ามันเหมาะกับในsignedหรือunsigned(โปรโมชั่นจํานวนเต็ม) บน32เครื่องบิตที่แท้จริงจะเป็น0x80000000 unsigned2 ความสมบูรณ์ของ-0x80000000อยู่0x80000000 บนเครื่อง 32 บิต ดังนั้นการเปรียบเทียบbal < INT32_MINอยู่ระหว่างsignedและunsignedและก่อนการเปรียบเทียบตามกฎซีจะถูกแปลงเป็นunsigned intlong long

C11: 6.3.1.8/1:

[... ] มิฉะนั้นหากประเภทของตัวถูกดำเนินการที่มีประเภทจำนวนเต็มที่ลงนามสามารถเป็นตัวแทนของค่าทั้งหมดของประเภทของตัวถูกดำเนินการที่มีประเภทจำนวนเต็มไม่ได้ลงนามแล้วตัวถูกดำเนินการกับประเภทจำนวนเต็มไม่ได้ลงนามจะถูกแปลงเป็นชนิดของตัวถูกดำเนินการ ประเภทจำนวนเต็มลงนาม

ดังนั้นอยู่เสมอbal < INT32_MINtrue

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.