Tại sao hành vi tràn số nguyên không dấu được xác định nhưng tràn số nguyên đã ký không?


209

Tràn số nguyên không dấu được xác định rõ bởi cả hai tiêu chuẩn C và C ++. Ví dụ: trạng thái tiêu chuẩn C99 ( §6.2.5/9)

Một tính toán liên quan đến các toán hạng không dấu không bao giờ có thể vượt quá, vì một kết quả không thể được biểu thị bằng loại số nguyên không dấu kết quả được giảm modulo số lớn hơn một giá trị lớn nhất có thể được biểu thị bằng loại kết quả.

Tuy nhiên, cả hai tiêu chuẩn đều nêu rằng tràn số nguyên đã ký là hành vi không xác định. Một lần nữa, từ tiêu chuẩn C99 ( §3.4.3/1)

Một ví dụ về hành vi chưa hoàn thành là hành vi trên số nguyên trên fl ow

Có một lý do lịch sử hoặc (thậm chí tốt hơn!) Cho sự khác biệt này?


50
Có lẽ bởi vì có nhiều hơn một cách biểu diễn số nguyên đã ký. Cách nào không được chỉ định trong tiêu chuẩn, ít nhất là không trong C ++.
juanchopanza


7
Những gì juanchopanza nói có ý nghĩa. Theo tôi hiểu, tiêu chuẩn C ban đầu trong một phần lớn được mã hóa thực tiễn hiện có. Nếu tất cả các triển khai tại thời điểm đó đồng ý về những gì "tràn" không dấu nên làm, đó là một lý do tốt để làm cho nó được chuẩn hóa. Họ đã không đồng ý về những gì đã ký tràn nên làm, vì vậy điều đó không đạt được trong tiêu chuẩn.

2
@DavidElliman Gói bổ sung không được chỉ định cũng có thể dễ dàng phát hiện ( if (a + b < a)). Tràn về nhân là khó cho cả hai loại đã ký và không dấu.

5
@DavidElliman: Đây không chỉ là vấn đề về việc bạn có thể phát hiện ra nó hay không, mà kết quả là gì. Trong một dấu hiệu + giá trị thực hiện, MAX_INT+1 == -0trong khi trên phần bổ sung của hai, nó sẽ làINT_MIN
David Rodríguez - dribeas

Câu trả lời:


163

Lý do lịch sử là hầu hết các triển khai C (trình biên dịch) chỉ sử dụng bất kỳ hành vi tràn nào là dễ thực hiện nhất với biểu diễn số nguyên mà nó sử dụng. Việc triển khai C thường được sử dụng cùng một biểu diễn được sử dụng bởi CPU - vì vậy hành vi tràn theo sau biểu diễn số nguyên được sử dụng bởi CPU.

Trong thực tế, chỉ có các đại diện cho các giá trị đã ký có thể khác nhau tùy theo cách thực hiện: bổ sung của một người, bổ sung hai, cường độ ký hiệu. Đối với loại không dấu, không có lý do nào để tiêu chuẩn cho phép biến thể vì chỉ có một biểu diễn nhị phân rõ ràng (tiêu chuẩn chỉ cho phép biểu diễn nhị phân).

Báo giá có liên quan:

C99 6.2.6.1:3 :

Các giá trị được lưu trữ trong các trường bit không dấu và các đối tượng của kiểu không dấu char sẽ được biểu diễn bằng ký hiệu nhị phân thuần túy.

C99 6.2.6.2:2 :

Nếu bit dấu là một, giá trị sẽ được sửa đổi theo một trong các cách sau:

- giá trị tương ứng với bit dấu 0 bị phủ định ( dấu và độ lớn );

- bit dấu có giá trị - (2 N ) ( phần bù hai );

- bit dấu có giá trị - (2 N - 1) ( phần bù của một người ).


Ngày nay, tất cả các bộ xử lý sử dụng biểu diễn bổ sung của hai, nhưng tràn số học đã ký vẫn chưa được xác định và các nhà sản xuất trình biên dịch muốn nó không được xác định bởi vì họ sử dụng tính không xác định này để giúp tối ưu hóa. Xem ví dụ bài đăng trên blog này của Ian Lance Taylor hoặc khiếu nại này của Agner Fog và câu trả lời cho báo cáo lỗi của anh ấy.


6
Tuy nhiên, lưu ý quan trọng ở đây là vẫn không có kiến trúc nào trong thế giới hiện đại sử dụng bất cứ thứ gì ngoài số học có chữ ký bổ sung của 2. Rằng các tiêu chuẩn ngôn ngữ vẫn cho phép thực hiện trên ví dụ PDP-1 là một tạo tác lịch sử thuần túy.
Andy Ross

9
@AndyRoss nhưng vẫn có các hệ thống (trình biên dịch OS +, được thừa nhận có lịch sử cũ) với phần bổ sung và bản phát hành mới vào năm 2013. Một ví dụ: OS 2200.
ouah

3
@Andy Ross bạn có muốn xem xét "không có kiến ​​trúc ... sử dụng bất cứ thứ gì ngoài phần bổ sung của 2 ..." ngày nay bao gồm gam của DSP và bộ xử lý nhúng không?
chux - Phục hồi Monica

11
@AndyRoss: Mặc dù có các kiến ​​trúc của No no sử dụng bất cứ thứ gì ngoài phần bổ sung 2 giây (đối với một số định nghĩa của không có gì), chắc chắn có các kiến trúc DSP sử dụng số học bão hòa cho các số nguyên đã ký.
Stephen Canon

10
Số học ký kết bão hòa chắc chắn là phù hợp với tiêu chuẩn. Tất nhiên các hướng dẫn gói phải được sử dụng cho số học không dấu, nhưng trình biên dịch luôn có thông tin để biết liệu số học không dấu hoặc đã ký có được thực hiện hay không, vì vậy nó chắc chắn có thể chọn hướng dẫn phù hợp.
phê

15

Ngoài câu trả lời hay của Pascal (mà tôi chắc chắn là động lực chính), cũng có thể một số bộ xử lý gây ra ngoại lệ đối với tràn số nguyên đã ký, tất nhiên sẽ gây ra sự cố nếu trình biên dịch phải "sắp xếp hành vi khác" ( ví dụ: sử dụng các hướng dẫn bổ sung để kiểm tra mức tràn tiềm năng và tính toán khác nhau trong trường hợp đó).

Cũng cần lưu ý rằng "hành vi không xác định" không có nghĩa là "không hoạt động". Nó có nghĩa là việc thực hiện được phép làm bất cứ điều gì nó thích trong tình huống đó. Điều này bao gồm thực hiện "điều đúng đắn" cũng như "gọi cảnh sát" hoặc "đâm". Hầu hết các trình biên dịch, khi có thể, sẽ chọn "làm điều đúng", giả sử rằng điều đó tương đối dễ xác định (trong trường hợp này là như vậy). Tuy nhiên, nếu bạn đang có quá nhiều trong tính toán, điều quan trọng là phải hiểu điều gì thực sự dẫn đến và trình biên dịch có thể làm gì đó ngoài những gì bạn mong đợi (và điều này có thể phụ thuộc rất nhiều vào phiên bản trình biên dịch, cài đặt tối ưu hóa, v.v.) .


23
Mặc dù vậy, trình biên dịch không muốn bạn dựa vào chúng để thực hiện đúng và hầu hết trong số chúng sẽ hiển thị cho bạn ngay khi bạn biên dịch int f(int x) { return x+1>x; }với tối ưu hóa. GCC và ICC làm, với các tùy chọn mặc định, tối ưu hóa các mục trên return 1;.
Pascal Cuoq

1
Đối với một chương trình ví dụ cho kết quả khác nhau khi gặp phải tình trạng inttràn tùy thuộc vào mức độ tối ưu hóa, hãy xem ideone.com/cki8nM Tôi nghĩ rằng điều này chứng tỏ rằng câu trả lời của bạn đưa ra lời khuyên tồi.
Magnus Hoff

Tôi đã sửa đổi phần đó một chút.
Thảm Petersson

Nếu một C cung cấp một phương tiện để khai báo một số nguyên "gói hai ký tự bổ sung", thì không có nền tảng nào có thể chạy C cả nên gặp nhiều khó khăn khi hỗ trợ nó ít nhất là hiệu quả vừa phải. Chi phí hoạt động thêm sẽ đủ để mã không nên sử dụng loại như vậy khi không yêu cầu hành vi gói, nhưng hầu hết các thao tác trên hai số nguyên bổ sung đều giống hệt với số nguyên trên một số nguyên không dấu, ngoại trừ so sánh và khuyến mãi.
supercat

1
Các giá trị âm cần tồn tại và "hoạt động" để trình biên dịch hoạt động chính xác, Tất nhiên là hoàn toàn có thể làm việc xung quanh việc thiếu các giá trị đã ký trong bộ xử lý và sử dụng các giá trị không dấu, như là bổ sung hoặc bổ sung twos, tùy theo điều kiện nào ý nghĩa dựa trên những gì các tập lệnh là. Việc này thường chậm hơn đáng kể so với việc hỗ trợ phần cứng cho nó, nhưng nó không khác gì các bộ xử lý không hỗ trợ điểm nổi trong phần cứng, hoặc tương tự - nó chỉ cần thêm rất nhiều mã.
Thảm Petersson

10

Trước hết, xin lưu ý rằng C11 3.4.3, giống như tất cả các ví dụ và ghi chú chân, không phải là văn bản quy phạm và do đó không liên quan đến trích dẫn!

Văn bản có liên quan nói rằng tràn số nguyên và số float là hành vi không xác định là:

C11 6.5 / 5

Nếu một điều kiện ngoại lệ xảy ra trong quá trình đánh giá biểu thức (nghĩa là, nếu kết quả không được xác định theo toán học hoặc không nằm trong phạm vi giá trị đại diện cho loại của nó), thì hành vi không được xác định.

Một cách làm rõ về hành vi của các loại số nguyên không dấu đặc biệt có thể được tìm thấy ở đây:

C11 6.2.5 / 9

Phạm vi của các giá trị không âm của loại số nguyên đã ký là một phạm vi con của loại số nguyên không dấu tương ứng và biểu diễn của cùng một giá trị trong mỗi loại là như nhau. Một tính toán liên quan đến toán hạng không dấu không bao giờ có thể tràn, bởi vì một kết quả không thể được biểu thị bằng loại số nguyên không dấu kết quả được giảm modulo số lớn hơn một giá trị lớn nhất có thể được biểu thị bằng loại kết quả.

Điều này làm cho các kiểu số nguyên không dấu là một trường hợp đặc biệt.

Cũng lưu ý rằng có một ngoại lệ nếu bất kỳ loại nào được chuyển đổi thành loại đã ký và giá trị cũ không còn có thể được biểu diễn. Hành vi sau đó chỉ đơn thuần là xác định thực hiện, mặc dù tín hiệu có thể được đưa ra.

C11 6.3.1.3

6.3.1.3 Số nguyên đã ký và không dấu

Khi một giá trị với loại số nguyên được chuyển đổi sang loại số nguyên khác ngoài _Bool, nếu giá trị có thể được biểu thị bằng loại mới, nó không thay đổi.

Mặt khác, nếu loại mới không được ký, giá trị được chuyển đổi bằng cách lặp lại hoặc trừ đi nhiều hơn một giá trị tối đa có thể được biểu thị trong loại mới cho đến khi giá trị nằm trong phạm vi của loại mới.

Mặt khác, loại mới được ký và giá trị không thể được biểu diễn trong đó; hoặc kết quả là xác định thực hiện hoặc tín hiệu xác định thực hiện được đưa ra.


6

Ngoài các vấn đề khác được đề cập, việc có gói toán không dấu làm cho các kiểu số nguyên không dấu hoạt động như các nhóm đại số trừu tượng (có nghĩa là, trong số các điều khác, đối với bất kỳ cặp giá trị nào XY, sẽ tồn tại một số giá trị khác Znhư vậy X+Z, nếu được đúc đúng , bằng YY-Zsẽ, nếu đúc đúng, bằngX). Nếu các giá trị không dấu chỉ là các loại vị trí lưu trữ và không phải là các loại biểu thức trung gian (ví dụ: nếu không có loại không dấu tương đương với loại số nguyên lớn nhất và các phép toán số học trên các loại không dấu được xử lý như thể lần đầu tiên chúng được chuyển đổi thành các loại có chữ ký lớn hơn, thì ở đó sẽ không cần nhiều hành vi gói được xác định, nhưng thật khó để thực hiện các phép tính trong một loại không có ví dụ như nghịch đảo phụ gia.

Điều này giúp ích trong các tình huống trong đó hành vi bao quanh thực sự hữu ích - ví dụ với số thứ tự TCP hoặc thuật toán nhất định, chẳng hạn như tính toán băm. Nó cũng có thể giúp ích trong các tình huống cần thiết để phát hiện tràn, vì thực hiện tính toán và kiểm tra xem chúng có tràn hay không thường dễ hơn kiểm tra trước xem chúng có tràn hay không, đặc biệt là nếu các phép tính liên quan đến loại số nguyên lớn nhất hiện có.


Tôi không hoàn toàn làm theo - tại sao nó lại có tác dụng nghịch đảo phụ gia? Tôi thực sự không thể nghĩ về bất kỳ tình huống nào trong đó hành vi tràn thực sự hữu ích ...
sleske

@sleske: Sử dụng số thập phân cho khả năng đọc của con người, nếu đồng hồ năng lượng đọc 0003 và lần đọc trước là 9995, điều đó có nghĩa là -9992 đơn vị năng lượng đã được sử dụng hay 0008 đơn vị năng lượng đã được sử dụng? Có năng suất 0003-9995 0008 giúp dễ dàng tính kết quả sau. Có nó mang lại -9992 sẽ làm cho nó khó xử hơn một chút. Tuy nhiên, việc không thể làm điều đó sẽ khiến bạn cần phải so sánh 0003 với 9995, lưu ý rằng nó ít hơn, thực hiện phép trừ ngược, trừ kết quả đó từ 9999 và thêm 1.
supercat

@sleske: Nó cũng rất hữu ích cho cả người và trình biên dịch để có thể áp dụng các định luật liên kết, phân phối và giao hoán của số học để viết lại các biểu thức và đơn giản hóa chúng; ví dụ, nếu biểu thức a+b-cđược tính trong vòng một vòng lặp, nhưng bclà liên tục trong vòng lặp đó, nó có thể hữu ích để di chuyển tính toán của (b-c)bên ngoài vòng lặp, nhưng làm điều đó sẽ đòi hỏi trong số những thứ khác mà (b-c)mang lại một giá trị mà khi thêm vào a, sẽ mang lại a+b-c, do đó đòi hỏi phải ccó nghịch đảo phụ gia.
supercat

: Cảm ơn đã giải thích. Nếu tôi hiểu chính xác, tất cả các ví dụ của bạn đều cho rằng bạn thực sự muốn xử lý tràn. Trong hầu hết các trường hợp tôi đã gặp phải, tràn là không mong muốn và bạn muốn ngăn chặn nó, vì kết quả của một phép tính với tràn không hữu ích. Ví dụ, đối với đồng hồ năng lượng, bạn có thể muốn sử dụng một loại sao cho tràn không bao giờ xảy ra.
sleske

1
... sao cho (a+b)-cbằng a+(b-c)với giá trị số học của b-ccó thể biểu thị được trong loại hay không, sự thay thế sẽ có hiệu lực bất kể phạm vi giá trị có thể có cho (b-c).
supercat

1

Có lẽ một lý do khác cho lý do tại sao số học không dấu được xác định là bởi vì số không dấu tạo thành số nguyên modulo 2 ^ n, trong đó n là chiều rộng của số không dấu. Số chưa ký chỉ đơn giản là số nguyên được biểu diễn bằng chữ số nhị phân thay vì chữ số thập phân. Thực hiện các hoạt động tiêu chuẩn trong một hệ thống mô-đun được hiểu rõ.

Trích dẫn của OP đề cập đến thực tế này, nhưng cũng nhấn mạnh thực tế rằng chỉ có một cách duy nhất, rõ ràng, hợp lý để biểu diễn các số nguyên không dấu trong nhị phân. Ngược lại, các số đã ký thường được biểu diễn bằng cách sử dụng phần bù hai nhưng các lựa chọn khác có thể được mô tả trong tiêu chuẩn (mục 6.2.6.2).

Biểu diễn bổ sung của hai cho phép các hoạt động nhất định có ý nghĩa hơn ở định dạng nhị phân. Ví dụ, tăng số âm là giống với số dương (mong đợi trong điều kiện tràn). Một số thao tác ở cấp độ máy có thể giống nhau đối với các số đã ký và không dấu. Tuy nhiên, khi diễn giải kết quả của các hoạt động đó, một số trường hợp không có ý nghĩa - tràn tích cực và tiêu cực. Hơn nữa, kết quả tràn khác nhau tùy thuộc vào đại diện được ký bên dưới.


Để một cấu trúc là một trường, mọi phần tử của cấu trúc khác với danh tính phụ gia phải có nghịch đảo nhân. Một cấu trúc của số nguyên đồng dư mod N sẽ chỉ là một trường khi N là một hoặc nguyên tố [một trường suy biến khi N == 1]. Có bất cứ điều gì bạn cảm thấy tôi bỏ lỡ trong câu trả lời của tôi?
supercat

Bạn đúng rồi. Tôi đã bị nhầm lẫn bởi các mô-đun quyền lực chính. Phản hồi ban đầu được chỉnh sửa.
YTH

Thêm khó hiểu ở đây là có một lĩnh vực trật tự 2 ^ n, nó chỉ là không vòng đẳng cấu với số nguyên modulo 2 ^ n.
Kevin Ventullo

Và, 2 ^ 31-1 là một Mersenne Prime (nhưng 2 ^ 63-1 không phải là số nguyên tố). Vì vậy, ý tưởng ban đầu của tôi đã bị hủy hoại. Ngoài ra, kích thước số nguyên đã khác nhau trong ngày. Vì vậy, ý tưởng của tôi là xét lại tốt nhất.
YTH

Thực tế là các số nguyên không dấu tạo thành một vòng (không phải là trường), lấy phần có thứ tự thấp cũng mang lại một vòng và thực hiện các thao tác trên toàn bộ giá trị và sau đó cắt ngắn sẽ hoạt động tương đương với thực hiện các thao tác trên chỉ phần dưới, là IMHO gần như chắc chắn cân nhắc.
supercat
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.