Tràn số nguyên có dấu vẫn là hành vi không xác định trong C ++?


82

Như chúng ta biết, tràn số nguyên có dấu là hành vi không xác định . Nhưng có điều gì đó thú vị trong cstdinttài liệu C ++ 11 :

kiểu số nguyên có dấu với độ rộng chính xác 8, 16, 32 và 64 bit tương ứng không có bit đệm và sử dụng phần bù của 2 cho các giá trị âm (chỉ được cung cấp nếu việc triển khai hỗ trợ trực tiếp kiểu)

Xem liên kết

Và đây là câu hỏi của tôi: kể từ khi tiêu chuẩn nói rõ ràng rằng int8_t, int16_t, int32_tint64_tsố âm là 2 của bổ sung, vẫn còn tràn của các loại một hành vi không xác định?

Chỉnh sửa Tôi đã kiểm tra Tiêu chuẩn C ++ 11 và C11 và đây là những gì tôi tìm thấy:

C ++ 11, §18.4.1:

Tiêu đề xác định tất cả các chức năng, kiểu và macro giống như 7.20 trong tiêu chuẩn C.

C11, §7.20.1.1:

Tên typedef intN_tchỉ định một kiểu số nguyên có dấu với độ rộng N, không có bit đệm và biểu diễn phần bù của hai. Do đó, int8_tbiểu thị một kiểu số nguyên có dấu với độ rộng chính xác là 8 bit.


14
Đừng bao giờ quên rằng tài liệu chính duy nhất cho C ++ là tiêu chuẩn. Mọi thứ khác, ngay cả wiki như CppReference, đều là nguồn thứ cấp. Điều đó không có nghĩa là nó sai; chỉ là không hoàn toàn đáng tin cậy.
Nicol Bolas

Tôi mong đợi nó là UB, không có miễn trừ cho các loại này trong C, tôi không hiểu tại sao C ++ lại thêm một loại.
Daniel Fischer


3
Tôi hơi bối rối: đâu là động từ trong câu "kiểu số nguyên có dấu với chiều rộng chính xác là 8, 16, 32 và 64 bit tương ứng với không có bit đệm và sử dụng phần bổ sung của 2 cho các giá trị âm (chỉ được cung cấp nếu triển khai hỗ trợ trực tiếp loại)? " Có thiếu một chút? Nó có nghĩa là gì?
YSC

C ++ 11 dựa trên C99, không phải C11. Nhưng dù sao thì điều này cũng không quan trọng
LF

Câu trả lời:


80

vẫn còn tràn các loại này là một hành vi không xác định?

Đúng. Mỗi Đoạn 5/4 của Tiêu chuẩn C ++ 11 (về bất kỳ biểu thức nào nói chung):

Nếu trong quá trình đánh giá một biểu thức, kết quả không được xác định về mặt toán học hoặc không nằm trong phạm vi giá trị có thể biểu diễn cho kiểu của nó, thì hành vi đó là không xác định . [...]

Thực tế là biểu diễn phần bù của hai được sử dụng cho các kiểu có dấu đó không có nghĩa là mô đun số học 2 ^ n được sử dụng khi đánh giá các biểu thức của các kiểu đó.

Mặt khác, liên quan đến số học không dấu , Tiêu chuẩn quy định rõ ràng rằng (Đoạn 3.9.1 / 4):

Các số nguyên không dấu, được khai báo unsigned, sẽ tuân theo luật của modulo số học 2 ^ n trong đó n là số bit trong biểu diễn giá trị của kích thước cụ thể của số nguyên

Điều này có nghĩa là kết quả của một phép toán số học không dấu luôn được " xác định về mặt toán học " và kết quả luôn nằm trong phạm vi có thể biểu diễn; do đó, 5/4 không được áp dụng. Chú thích chân trang 46 giải thích điều này:

46) Điều này ngụ ý rằng số học không dấu không tràn vì kết quả không thể biểu diễn bằng kiểu số nguyên không dấu kết quả được giảm theo mô đun số lớn hơn một giá trị lớn nhất có thể được biểu diễn bằng kiểu số nguyên không dấu kết quả.


1
Đoạn này cũng ngụ ý rằng tràn không dấu là không xác định, không phải vậy.
Archie,

8
@Archie: Không hẳn, vì các giá trị không dấu được xác định theo mô-đun trong phạm vi không dấu.
Lightness Races in Orbit

3
@Archie: Tôi đã cố gắng để làm rõ, nhưng về cơ bản bạn có câu trả lời từ LightnessRacesinOrbit
Andy Prowl

1
Nó thực sự không quan trọng nếu tràn unsigned được định nghĩa hay không nếu nó không thể xảy ra do modulo tính ...
Aconcagua

1
Có những phép toán không dấu mà kết quả của nó không được "định nghĩa về mặt toán học" - đặc biệt là phép chia cho 0 - vì vậy có lẽ từ ngữ của bạn không đúng như ý bạn trong câu đó. ITYM khi kết quả được xác định theo toán học , thì nó cũng được định nghĩa trong C ++.
Toby Speight

22

Chỉ bởi vì một kiểu được xác định để sử dụng biểu diễn bổ sung 2s, nó không tuân theo việc tràn số học trong kiểu đó được xác định.

Hành vi không xác định của tràn số học có dấu được sử dụng để cho phép tối ưu hóa; ví dụ, trình biên dịch có thể giả định rằng if a > bthen a + 1 > balso; điều này không phù hợp với số học không dấu khi mà lần kiểm tra thứ hai sẽ cần được thực hiện vì khả năng a + 1có thể xảy ra xung quanh 0. Ngoài ra, một số nền tảng có thể tạo ra một dấu hiệu bẫy khi tràn số học (xem ví dụ: http://www.gnu.org/software/libc/manual/html_node/Program-Error-Signals.html ); tiêu chuẩn tiếp tục cho phép điều này xảy ra.


5
Có thể đáng chú ý là nhiều người "lo lắng" nhiều hơn về khả năng mắc bẫy, nhưng các giả định của trình biên dịch thực sự xảo quyệt hơn (một trong những lý do tôi ước có một danh mục giữa Hành vi được xác định thực hiện và Hành vi không được xác định - không giống như hành vi do triển khai xác định yêu cầu các triển khai cụ thể để thực hiện điều gì đó theo kiểu tài liệu nhất quán, tôi muốn một hành vi "hạn chế triển khai" sẽ yêu cầu triển khai để chỉ định mọi thứ có thể xảy ra do hậu quả của điều gì đó (các thông số kỹ thuật có thể bao gồm Hành vi không xác định một cách rõ ràng, nhưng .. .
supercat

3
... việc triển khai sẽ được khuyến khích cụ thể hơn khi thực tế). Trên một phần cứng mà số bổ sung của hai số sẽ "quấn" một cách tự nhiên, không có lý do hợp lý nào cho việc mã muốn kết quả số nguyên được bao gói để thực thi nhiều lệnh đang cố gắng thực hiện mà không làm tràn số nguyên, một phép tính mà phần cứng có thể thực hiện chỉ trong một hoặc hai lệnh .
supercat

1
@supercat Trên thực tế, mã muốn kết quả được bao bọc có thể (trên các CPU bổ sung của 2) chỉ ép các toán hạng thành các kiểu không dấu tương ứng và thực hiện thao tác (và sau đó ép kiểu trở lại, nhận giá trị do triển khai xác định): điều này hoạt động cho phép cộng, trừ và nhân . Vấn đề duy nhất là với phân chia, mô-đun và các chức năng như abs. Đối với những phép toán đó khi nó hoạt động, nó không yêu cầu nhiều hướng dẫn hơn so với số học có dấu.
Ruslan

@Ruslan: Trong trường hợp mã cần kết quả được bao bọc chính xác, việc ép kiểu thành không dấu sẽ xấu nhưng không nhất thiết phải tạo thêm mã. Một vấn đề lớn hơn sẽ là với mã cần nhanh chóng xác định các ứng viên "có khả năng thú vị", mã này sẽ dành phần lớn thời gian để từ chối các ứng viên không thú vị. Nếu một người cho phép trình biên dịch tự do tùy ý giữ hoặc loại bỏ độ chính xác bổ sung với các giá trị số nguyên có dấu, nhưng yêu cầu truyền trở lại kiểu số nguyên cắt bớt bất kỳ độ chính xác nào như vậy, điều đó sẽ cho phép hầu hết các tối ưu hóa hữu ích sẽ đạt được bằng cách tạo tràn UB , ...
supercat

... nhưng sẽ cho phép mã cần gói chính xác sử dụng một lần ép kiểu thay vì hai lần (ví dụ: (int)(x+y)>zsẽ so sánh kết quả được gói) và cũng sẽ cho phép các lập trình viên viết x+y>ztrong các trường hợp có thể chấp nhận được mã cho kết quả 0 hoặc 1 trong trường hợp tràn miễn là nó không có tác dụng phụ nào khác . Nếu 0 hoặc 1 là một kết quả chấp nhận được như nhau, việc cho phép người lập trình viết điều đó thay vì (long)x+y>zhoặc (int)((unsigned)x+y)>zsẽ cho phép trình biên dịch chọn bất kỳ hàm nào trong số các hàm sau rẻ hơn trong bất kỳ ngữ cảnh nhất định nào [mỗi hàm sẽ rẻ hơn trong một số trường hợp].
supercat

1

Tôi cá là vậy.

Từ tài liệu tiêu chuẩn (trang 4 và 5):

1.3.24 hành vi không xác định

hành vi mà tiêu chuẩn này không áp đặt yêu cầu

[Lưu ý: Hành vi không xác định có thể xảy ra khi Tiêu chuẩn này bỏ qua bất kỳ định nghĩa rõ ràng nào về hành vi hoặc khi một chương trình sử dụng cấu trúc sai hoặc dữ liệu sai. Hành vi không xác định được phép có phạm vi từ việc bỏ qua hoàn toàn tình huống với kết quả không thể đoán trước, đến hành vi trong quá trình dịch hoặc thực thi chương trình theo cách thức được lập thành văn bản đặc trưng của môi trường (có hoặc không đưa ra thông báo chẩn đoán), đến việc chấm dứt bản dịch hoặc thực thi (với việc phát hành của một thông báo chẩn đoán). Nhiều cấu trúc chương trình sai lầm không tạo ra hành vi không xác định; họ bắt buộc phải được chẩn đoán. - lưu ý cuối]

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.