Tại sao là 0 <-0x80000000?


253

Tôi có một chương trình đơn giản dưới đây:

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

Điều kiện if(bal < INT32_MIN )luôn luôn đúng. Làm thế nào là nó có thể?

Nó hoạt động tốt nếu tôi thay đổi macro thành:

#define INT32_MIN        (-2147483648L)

Bất cứ ai có thể chỉ ra vấn đề?


3
Bao nhiêu là CHAR_BIT * sizeof(int)?
5gon12eder

1
Bạn đã thử in ra bal?
Ryan Fitzpatrick

10
IMHO điều thú vị hơn là nó là đúng chỉ cho -0x80000000, nhưng sai cho -0x80000000L, -2147483648-2147483648L(gcc 4.1.2), vì vậy câu hỏi là: tại sao là int đen -0x80000000khác với int đen -2147483648?
Andreas Fester

2
@Bathsheba Tôi chỉ chạy chương trình trên trình biên dịch trực tuyến tutorialspoint.com/codingground.htm
Jayesh Bhoi

2
Nếu bạn đã từng nhận thấy rằng (một số hóa thân của) <limits.h>định nghĩa INT_MIN(-2147483647 - 1), bây giờ bạn biết tại sao.
zwol

Câu trả lời:


363

Điều này khá tinh tế.

Mỗi số nguyên trong chương trình của bạn có một loại. Loại nào nó được quy định bởi một bảng trong 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

Nếu một số bằng chữ không thể vừa với intloại mặc định , nó sẽ thử loại lớn hơn tiếp theo như được chỉ ra trong bảng trên. Vì vậy, đối với chữ số nguyên thập phân thông thường, nó sẽ như sau:

  • Thử int
  • Nếu nó không phù hợp, hãy thử long
  • Nếu nó không phù hợp, hãy thử long long.

Hex văn học hành xử khác nhau mặc dù! Nếu nghĩa đen không thể phù hợp với một loại đã ký như trước int, nó sẽ thử unsigned inttrước khi chuyển sang thử loại lớn hơn. Xem sự khác biệt trong bảng trên.

Vì vậy, trên hệ thống 32 bit, nghĩa đen của bạn 0x80000000là loại unsigned int.

Điều này có nghĩa là bạn có thể áp dụng toán tử đơn nguyên -trên chữ mà không cần gọi hành vi được xác định theo cách thực hiện, như cách khác khi làm tràn số nguyên đã ký. Thay vào đó, bạn sẽ nhận được giá trị 0x80000000, một giá trị tích cực.

bal < INT32_MINgọi các chuyển đổi số học thông thường và kết quả của biểu thức 0x80000000được thăng cấp từ unsigned intsang long long. Giá trị 0x80000000được bảo toàn và 0 nhỏ hơn 0x80000000, do đó có kết quả.

Khi bạn thay thế bằng chữ 2147483648Lbạn sử dụng ký hiệu thập phân và do đó trình biên dịch không chọn unsigned int, mà là cố gắng để phù hợp với nó bên trong a long. Ngoài ra hậu tố L nói rằng bạn muốn một long nếu có thể . Hậu tố L thực sự có các quy tắc tương tự nếu bạn tiếp tục đọc bảng được đề cập trong 6.4.4.1: nếu số không phù hợp với yêu cầu long, trong trường hợp 32 bit, trình biên dịch sẽ cung cấp cho bạn long longvị trí của nó sẽ vừa tốt


3
"... thay thế bằng chữ -2147483648L, bạn rõ ràng nhận được một chữ dài, được ký." Hmmm, Trong một 32-bit longhệ thống 2147483648L, sẽ không phù hợp trong một long, vì vậy nó trở thành long long, sau đó các -được áp dụng - hoặc vì vậy tôi nghĩ.
chux - Phục hồi Monica

2
@ASH Vì số int tối đa có thể có là sau đó 0x7FFFFFFF. Hãy tự thử:#include <limits.h> printf("%X\n", INT_MAX);
Lundin

5
@ASH Đừng nhầm lẫn biểu diễn thập lục phân của số nguyên trong mã nguồn với biểu diễn nhị phân cơ bản của một số đã ký. Nghĩa đen 0x7FFFFFFFkhi được viết bằng mã nguồn luôn là một số dương, nhưng tất nhiên intbiến của bạn có thể chứa các số nhị phân thô có giá trị 0xFFFFFFFF.
Lundin

2
@ASH ìnt n = 0x80000000buộc chuyển đổi từ chữ không dấu sang loại đã ký. Điều gì sẽ xảy ra là tùy thuộc vào trình biên dịch của bạn - đó là hành vi được xác định theo thực hiện. Trong trường hợp này, nó đã chọn hiển thị toàn bộ nghĩa đen vào int, ghi đè bit dấu. Trên các hệ thống khác, có thể không thể biểu diễn loại và bạn gọi hành vi không xác định - chương trình có thể bị sập. Bạn sẽ có hành vi tương tự nếu bạn làm int n=2147483648;như vậy nó hoàn toàn không liên quan đến ký hiệu hex.
Lundin

3
Giải thích về cách -áp dụng unary cho số nguyên không dấu có thể được mở rộng một chút. Tôi đã luôn luôn giả định (mặc dù may mắn là không bao giờ dựa vào giả định) rằng các giá trị không dấu sẽ được "thăng cấp" thành các giá trị đã ký hoặc có thể kết quả sẽ không được xác định. (Thành thật mà nói, đó phải là một lỗi biên dịch; - 3uthậm chí nó có nghĩa là gì?)
Kyle Strand

27

0x80000000là một unsignednghĩa đen có giá trị 2147483648.

Áp dụng phép trừ unary trên điều này vẫn cung cấp cho bạn một loại không dấu với giá trị khác không. (Trong thực tế, đối với một giá trị khác không x, giá trị bạn kết thúc là UINT_MAX - x + 1.)


23

Số nguyên 0x80000000này có loại unsigned int.

Theo tiêu chuẩn C (hằng số nguyên 6.4.4.1)

5 Loại hằng số nguyên là đầu tiên của danh sách tương ứng trong đó giá trị của nó có thể được biểu diễn.

Và hằng số nguyên này có thể được biểu diễn bằng loại unsigned int.

Vì vậy, biểu hiện này

-0x80000000có cùng unsigned intloại. Ngoài ra, nó có cùng giá trị 0x80000000trong biểu diễn phần bù của hai phép tính theo cách sau

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

Điều này có tác dụng phụ nếu viết chẳng hạn

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

Kết quả sẽ là một lần nữa INT_MIN.

Do đó trong điều kiện này

bal < INT32_MIN

có so sánh 0với giá trị không dấu được0x80000000 chuyển đổi thành kiểu int dài theo quy tắc chuyển đổi số học thông thường.

Rõ ràng là 0 nhỏ hơn 0x80000000.


12

Hằng số 0x80000000là loại unsigned int. Nếu chúng ta thực hiện -0x80000000và làm bài toán 2s về nó, chúng ta sẽ nhận được điều này:

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

Vì vậy -0x80000000 == 0x80000000. Và so sánh (0 < 0x80000000)(vì 0x80000000không dấu) là đúng.


Điều này giả sử 32 bit int. Mặc dù đó là một lựa chọn rất phổ biến, trong bất kỳ triển khai cụ intthể nào cũng có thể hẹp hơn hoặc rộng hơn. Đó là một phân tích chính xác cho trường hợp đó, tuy nhiên.
John Bollinger

Điều này không liên quan đến mã của OP, -0x80000000là số học không dấu. ~0x800000000là mã khác nhau.
MM

Đây có vẻ là câu trả lời tốt nhất và chính xác cho tôi chỉ đơn giản là đặt. @MM anh đang giải thích làm thế nào để bổ sung twos. Câu trả lời này đặc biệt giải quyết những gì dấu hiệu tiêu cực đang làm với số.
Bạch tuộc

@Octopus dấu âm không áp dụng phần bù 2 cho số (!) Mặc dù điều này có vẻ rõ ràng, nhưng nó không mô tả những gì xảy ra trong mã -0x80000000! Trong thực tế, phần bổ sung của 2 hoàn toàn không liên quan đến câu hỏi này.
MM

12

Một điểm nhầm lẫn xảy ra trong suy nghĩ -là một phần của hằng số.

Trong đoạn mã dưới đây 0x80000000là hằng số. Loại của nó chỉ được xác định trên đó. Sau đó -được áp dụng và không thay đổi loại .

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

Các hằng số nguyên chưa được đánh dấu là dương.

Nếu đó là số thập phân, sau đó loại giao là loại đầu tiên mà sẽ giữ nó: int, long, long long.

Nếu hằng số là bát phân hay thập lục phân, nó được loại đầu tiên mà giữ nó: int, unsigned, long, unsigned long, long long, unsigned long long.

0x80000000, trên hệ thống của OP có loại unsignedhoặc unsigned long. Dù bằng cách nào, nó là một số loại không dấu.

-0x80000000cũng là một số giá trị khác không và là một số loại không dấu, nó lớn hơn 0. Khi mã so sánh với a long long, các giá trị không được thay đổi ở 2 mặt so sánh, điều đó 0 < INT32_MINlà đúng.


Một định nghĩa thay thế tránh hành vi tò mò này

#define INT32_MIN        (-2147483647 - 1)

Hãy để chúng tôi đi bộ trong vùng đất tưởng tượng trong một thời gian intunsigned48-bit.

Sau đó 0x80000000phù hợp với intvà đó là loại int. -0x80000000sau đó là một số âm và kết quả của việc in ra là khác nhau.

[Quay lại từ thật]

0x80000000phù hợp với một số loại không dấu trước một loại đã ký vì nó chỉ lớn hơn some_signed_MAXbên trong some_unsigned_MAX, nên nó là một loại không dấu.


8

C có một quy tắc rằng số nguyên có thể signedhoặc unsignedphụ thuộc vào việc nó phù hợp với signedhoặc unsigned(khuyến mãi số nguyên). Trên một 32máy -bit, nghĩa đen 0x80000000sẽ là unsigned. Phần bổ sung của 2 -0x800000000x80000000 trên máy 32 bit. Do đó, so sánh bal < INT32_MINlà giữa signedunsignedtrước khi so sánh theo quy tắc C unsigned intsẽ được chuyển đổi thành long long.

C11: 6.3.1.8/1:

[...] Mặt khác, nếu loại toán hạng có loại số nguyên đã ký có thể biểu thị tất cả các giá trị của loại toán hạng có loại số nguyên không dấu, thì toán hạng có loại số nguyên không dấu được chuyển đổi thành loại toán hạng với ký kiểu nguyên.

Do đó, bal < INT32_MINlà luôn luôn true.

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.