(-2147483648> 0) trả về true trong C ++?


241

-2147483648 là số nguyên nhỏ nhất cho loại số nguyên có 32 bit, nhưng có vẻ như nó sẽ tràn vào if(...)câu:

if (-2147483648 > 0)
    std::cout << "true";
else
    std::cout << "false";

Điều này sẽ in truetrong thử nghiệm của tôi. Tuy nhiên, nếu chúng ta chuyển -2147483648 thành số nguyên, kết quả sẽ khác:

if (int(-2147483648) > 0)
    std::cout << "true";
else
    std::cout << "false";

Điều này sẽ in false.

Tôi bối rối. Bất cứ ai có thể đưa ra một lời giải thích về điều này?


Cập nhật ngày 02-05-2012:

Cảm ơn ý kiến ​​của bạn, trong trình biên dịch của tôi, kích thước của int là 4 byte. Tôi đang sử dụng VC cho một số thử nghiệm đơn giản. Tôi đã thay đổi mô tả trong câu hỏi của tôi.

Đó là rất nhiều câu trả lời rất hay trong bài đăng này, AndreyT đã đưa ra một lời giải thích rất chi tiết về cách trình biên dịch sẽ hành xử trên đầu vào như vậy và cách thức số nguyên tối thiểu này được thực hiện. Mặt khác, qPCR4vir đã đưa ra một số "sự tò mò" liên quan và cách thức số nguyên được biểu diễn. Thật ấn tượng!


48
"Chúng ta đều biết rằng -2147483648 là số nguyên nhỏ nhất" Điều đó phụ thuộc vào kích thước của số nguyên.
orlp

14
"Chúng ta đều biết rằng -2147483648 là số nguyên nhỏ nhất" - Tôi nghĩ rằng không có số nguyên nhỏ nhất, vì có vô số trong số chúng ... Dù sao đi nữa.

@Inisheer Với 4 Byte số nguyên bạn có thể có một INT_MINsố -9223372036854775808, nếu CHAR_BITlà 16. Và ngay cả với CHAR_BIT == 8sizeof(int== 4) `bạn có thể nhận được -9223372036854775807vì C không yêu cầu 2-Complement số.
12431234123412341234123

Câu trả lời:


391

-2147483648không phải là "số". Ngôn ngữ C ++ không hỗ trợ các giá trị theo nghĩa đen.

-2147483648thực sự là một biểu thức: một giá trị theo nghĩa đen 2147483648với toán tử đơn nguyên -ở phía trước nó. Giá trị 2147483648rõ ràng là quá lớn đối với mặt tích cực của intphạm vi trên nền tảng của bạn. Nếu loại long intcó phạm vi lớn hơn trên nền tảng của bạn, trình biên dịch sẽ phải tự động cho rằng 2147483648long intloại. (Trong C ++ 11, trình biên dịch cũng sẽ phải xem xét long long intloại.) Điều này sẽ khiến trình biên dịch đánh giá -2147483648trong miền của loại lớn hơn và kết quả sẽ là âm, như người ta mong đợi.

Tuy nhiên, rõ ràng trong trường hợp của bạn, phạm vi của long intnó giống như phạm vi intvà nói chung, không có loại số nguyên nào có phạm vi lớn hơn inttrên nền tảng của bạn. Điều này chính thức có nghĩa là hằng số dương 2147483648tràn qua tất cả các loại số nguyên có sẵn, điều này có nghĩa là hành vi của chương trình của bạn không được xác định. (Một điều hơi lạ là đặc tả ngôn ngữ áp dụng cho hành vi không xác định trong các trường hợp như vậy, thay vì yêu cầu một thông báo chẩn đoán, nhưng đó là như vậy.)

Trong thực tế, có tính đến việc hành vi không được xác định, 2147483648có thể được hiểu là một số giá trị âm phụ thuộc vào việc thực hiện sẽ chuyển thành tích cực sau khi -áp dụng đơn phương cho nó. Ngoài ra, một số triển khai có thể quyết định thử sử dụng các loại không dấu để biểu thị giá trị (ví dụ: trong trình biên dịch C89 / 90 được yêu cầu sử dụng unsigned long int, nhưng không phải trong C99 hoặc C ++). Việc triển khai được phép làm bất cứ điều gì, vì dù sao hành vi không được xác định.

Là một lưu ý phụ, đây là lý do tại sao các hằng số như INT_MINthường được định nghĩa là

#define INT_MIN (-2147483647 - 1)

thay vì có vẻ đơn giản hơn

#define INT_MIN -2147483648

Thứ hai sẽ không hoạt động như dự định.


78
Đây cũng là lý do tại sao điều này được thực hiện : #define INT_MIN (-2147483647 - 1).
orlp

5
@ RichardJ.RossIII - với tiếng kêu có lẽ bạn đang nhận được một chữ 64-bit, vì nó quá lớn để phù hợp với một int. Việc triển khai của OP có thể không có loại 64 bit.
Carl Norum

1
@ RichardJ.RossIII: Tôi tin rằng hành vi này được xác định thực hiện / không xác định.
Oliver Charlesworth

3
Tôi chưa bao giờ nghĩ rằng "số âm" không được phân tích cú pháp như vậy. Tôi không thấy một lý do. Tôi hy vọng rằng nó -1.0được phân tích cú pháp như một giá trị kép âm, phải không?
leeme

6
@ qPCR4vir: Không. Như tôi đã viết trong nhận xét của mình cho câu trả lời của bạn, cả C và C ++ hiện đại đều không cho phép sử dụng các loại không dấu trong trường hợp này (với hằng số thập phân không trộn ). Chỉ tiêu chuẩn C (C89 / 90) đầu tiên được phép unsigned long inttrong bối cảnh này, nhưng trong C99, quyền này đã bị xóa. Các chữ không trộn lẫn trong C và C ++ được yêu cầu phải có các kiểu đã ký . Nếu bạn thấy loại không dấu ở đây khi một cái đã ký sẽ hoạt động, điều đó có nghĩa là trình biên dịch của bạn bị hỏng. Nếu bạn thấy loại không dấu ở đây khi không có loại đã ký sẽ hoạt động, thì đây chỉ là một biểu hiện cụ thể của hành vi không xác định.
AnT

43

Trình biên dịch (VC2012) quảng bá đến các số nguyên "tối thiểu" có thể chứa các giá trị. Trong trường hợp đầu tiên, signed int(và long int) không thể (trước khi áp dụng dấu hiệu), nhưng unsigned intcó thể: 2147483648unsigned int ???? kiểu. Trong thứ hai, bạn lực lượng inttừ unsigned.

const bool i= (-2147483648 > 0) ;  //   --> true

cảnh báo C4146: toán tử trừ đơn nguyên áp dụng cho loại không dấu , kết quả vẫn chưa được ký

Dưới đây là những "sự tò mò" liên quan:

const bool b= (-2147483647      > 0) ; //  false
const bool i= (-2147483648      > 0) ; //  true : result still unsigned
const bool c= ( INT_MIN-1       > 0) ; //  true :'-' int constant overflow
const bool f= ( 2147483647      > 0) ; //  true
const bool g= ( 2147483648      > 0) ; //  true
const bool d= ( INT_MAX+1       > 0) ; //  false:'+' int constant overflow
const bool j= ( int(-2147483648)> 0) ; //  false : 
const bool h= ( int(2147483648) > 0) ; //  false
const bool m= (-2147483648L     > 0) ; //  true 
const bool o= (-2147483648LL    > 0) ; //  false

Tiêu chuẩn C ++ 11 :

2.14.2 Số nguyên chữ [lex.icon]

Giáo dục

Một số nguyên là một chuỗi các chữ số không có phần dấu chấm hoặc số mũ. Một số nguyên có thể có một tiền tố chỉ định cơ sở của nó và một hậu tố chỉ định loại của nó.

Giáo dục

Kiểu của một 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.

nhập mô tả hình ảnh ở đây

Nếu một số nguyên không thể được đại diện bởi bất kỳ loại nào trong danh sách của nó và một loại số nguyên mở rộng (3.9.1) có thể đại diện cho giá trị của nó, thì nó có thể có loại số nguyên mở rộng đó. Nếu tất cả các loại trong danh sách cho chữ được ký, loại số nguyên mở rộng sẽ được ký. Nếu tất cả các loại trong danh sách cho chữ đều không được ký thì loại số nguyên mở rộng sẽ không được ký. Nếu danh sách chứa cả loại đã ký và loại không dấu, loại số nguyên mở rộng có thể được ký hoặc không dấu. Một chương trình không được định dạng nếu một trong các đơn vị dịch của nó chứa một số nguyên không thể biểu thị bằng bất kỳ loại được phép nào.

Và đây là các quy tắc khuyến mãi cho số nguyên trong tiêu chuẩn.

4.5 Chương trình khuyến mãi tích hợp [conv.prom]

Một prvalue của một kiểu số nguyên khác hơn bool, char16_t, char32_t, hoặc wchar_tcó số nguyên chuyển đổi cấp bậc (4.13) là thấp hơn cấp bậc int có thể được chuyển đổi sang một prvalue loại intnếu intcó thể đại diện cho tất cả các giá trị của các loại nguồn; mặt khác, giá trị nguồn có thể được chuyển đổi thành giá trị loại unsigned int.


3
@ qPCR4vir: Trong C89 / 90 các trình biên dịch được cho là loại sử dụng int, long int, unsigned long intđể đại diện cho các hằng số thập phân unsuffixed. Đó là ngôn ngữ duy nhất cho phép sử dụng các loại không dấu cho các hằng số thập phân không trộn. Trong C ++ 98, nó là inthoặc long int. Không có loại không dấu được phép. Cả C (bắt đầu từ C99) và C ++ đều không cho phép trình biên dịch sử dụng các kiểu không dấu trong ngữ cảnh này. Trình biên dịch của bạn, tất nhiên, miễn phí sử dụng các kiểu không dấu nếu không có kiểu nào được ký, nhưng đây vẫn chỉ là một biểu hiện cụ thể của hành vi không xác định.
AnT

@AndreyT. Tuyệt quá! Của couse, sự cứng nhắc của bạn. Là VC2012 bị hỏng?
qPCR4vir

@ qPCR4vir: AFAIK, VC2012 chưa phải là trình biên dịch C ++ 11 (phải không?), có nghĩa là nó phải sử dụng inthoặc long intđể đại diện 2147483648. Ngoài ra, AFAIK, trong cả VC2012 intlong intđều là loại 32 bit. Điều này có nghĩa là trong VC2012 nghĩa đen 2147483648sẽ dẫn đến hành vi không xác định . Khi hành vi không được xác định, trình biên dịch được phép làm bất cứ điều gì. Điều đó có nghĩa là VC2012 không bị hỏng. Nó chỉ đơn giản là đưa ra một thông điệp chẩn đoán sai lệch. Thay vì nói với bạn rằng hành vi không được xác định rõ ràng, nó đã quyết định sử dụng một loại không dấu.
AnT

@AndreyT: Bạn có nói rằng trình biên dịch có thể tự do phát ra ma quỷ mũi nếu mã nguồn chứa một chữ thập phân không trộn lẫn vượt quá giá trị tối đa của một chữ ký longvà không bắt buộc phải đưa ra chẩn đoán? Điều đó dường như bị phá vỡ.
supercat

Tương tự "cảnh báo C4146" trong VS2008 và "hằng số thập phân này không được ký chỉ trong ISO C90" trong G ++
gián điệp

6

Trong ngắn, 2147483648tràn đến -2147483648, và (-(-2147483648) > 0)true.

Đây là cách 2147483648trông giống như trong nhị phân.

Ngoài ra, trong trường hợp tính toán nhị phân đã ký, bit quan trọng nhất ("MSB") là bit dấu. Câu hỏi này có thể giúp giải thích tại sao.


4

Bởi vì -2147483648thực sự 2147483648với phủ định ( -) được áp dụng cho nó, con số không như bạn mong đợi. Nó thực sự tương đương với mã giả này:operator -(2147483648)

Bây giờ, giả sử trình biên dịch của bạn sizeof(int)bằng 4CHAR_BITđược định nghĩa là 8, điều đó sẽ làm 2147483648tràn giá trị đã ký tối đa của một số nguyên ( 2147483647). Vì vậy, tối đa cộng một là gì? Hãy làm việc với số nguyên khen 4 bit, 2 giây.

Chờ đợi! 8 tràn số nguyên! Chúng ta làm gì? Sử dụng biểu diễn không dấu của nó 1000và diễn giải các bit như một số nguyên đã ký. Đại diện này cho chúng ta -8áp dụng phủ định bổ sung 2s dẫn đến 8, như chúng ta đều biết, lớn hơn 0.

Đây là lý do tại sao <limits.h>(và <climits>) thường định nghĩa INT_MIN((-2147483647) - 1)- để số nguyên được ký tối đa ( 0x7FFFFFFF) bị phủ định ( 0x80000001), sau đó giảm ( 0x80000000).


Đối với số 4 bit, phủ định bổ sung của hai -8vẫn là -8.
Ben Voigt

Ngoại trừ -8 được hiểu là 0-8, không âm 8. Và 8 tràn ra một ký tự 4 bit được ký kết
Cole Johnson

Xem xét -(8)cái nào trong C ++ giống như -8- đó là phủ định được áp dụng cho một nghĩa đen, không phải là một nghĩa đen. Nghĩa đen là 8, không phù hợp với số nguyên 4 bit đã ký, vì vậy nó phải được bỏ dấu. Các mô hình là 1000. Cho đến nay câu trả lời của bạn là chính xác. Phủ định bổ sung của hai 1000bit trong 4 bit là 1000, không quan trọng nếu nó được ký hoặc không dấu. Câu trả lời của bạn, cho biết "diễn giải các bit như một số nguyên đã ký", điều này làm cho giá trị -8sau phủ định bổ sung của hai, giống như trước khi phủ định.
Ben Voigt

Tất nhiên, trong "4 bit C ++" không có "diễn giải các bit là một bước nguyên đã ký". Nghĩa đen trở thành loại nhỏ nhất có thể biểu thị nó, đó là số nguyên 4 bit không dấu . Giá trị của nghĩa đen là 8. Phủ định được áp dụng (modulo 16), dẫn đến câu trả lời cuối cùng là 8. Mã hóa vẫn là 1000 nhưng giá trị khác nhau vì loại không dấu được chọn.
Ben Voigt
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.