Câu hỏi Là dấu trừ số nguyên không xác định hành vi?


Tôi đã đi qua mã từ một người dường như tin rằng có một vấn đề trừ một số nguyên unsigned từ một số nguyên cùng loại khi kết quả sẽ là tiêu cực. Vì vậy, mã như thế này sẽ là không chính xác ngay cả khi nó xảy ra để làm việc trên hầu hết các kiến ​​trúc.

unsigned int To, Tf;

To = getcounter();
while (1) {
    Tf = getcounter();
    if ((Tf-To) >= TIME_LIMIT) {
        break;
    } 
}

Đây là báo giá mơ hồ chỉ có liên quan từ tiêu chuẩn C tôi có thể tìm thấy.

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

Tôi cho rằng người ta có thể lấy báo giá đó để có nghĩa là khi toán hạng bên phải lớn hơn thì thao tác được điều chỉnh để có ý nghĩa trong ngữ cảnh của các số bị cắt ngắn modulo.

I E.

0x0000 - 0x0001 == 0x 1 0000 - 0x0001 == 0xFFFF

trái ngược với việc sử dụng ngữ nghĩa phụ thuộc thực hiện:

0x0000 - 0x0001 == (chưa ký) (0 + -1) == (0xFFFF nhưng cũng 0xFFFE hoặc 0x8001)

Giải thích nào là đúng? Nó được định nghĩa ở tất cả?


76
2017-08-28 13:59


gốc


Việc lựa chọn các từ trong tiêu chuẩn là không may. Rằng nó "không bao giờ có thể tràn" có nghĩa là nó không phải là một tình huống lỗi. Sử dụng thuật ngữ trong tiêu chuẩn, thay vì làm tràn giá trị “kết thúc tốt đẹp”. - danorton


Các câu trả lời:


Kết quả phép trừ tạo ra một số âm trong một loại chưa ký được xác định rõ:

  1. [...] Một phép tính liên quan đến toán hạng chưa ký không bao giờ có thể tràn,   bởi vì kết quả không thể được biểu diễn bằng kiểu số nguyên không dấu kết quả là   giảm modulo số lớn hơn giá trị lớn nhất có thể   được thể hiện bằng kiểu kết quả.   (ISO / IEC 9899: 1999 (E) §6.2.5 / 9)

Bạn có thể thấy, (unsigned)0 - (unsigned)1 bằng -1 modulo UINT_MAX + 1 hoặc nói cách khác là UINT_MAX.

Lưu ý rằng mặc dù nó có nghĩa là "Một phép tính liên quan đến toán hạng unsigned không bao giờ có thể tràn", điều này có thể khiến bạn tin rằng nó chỉ áp dụng cho việc vượt quá giới hạn trên, điều này được trình bày như là một động lực đối với phần ràng buộc thực tế của câu: "kết quả không thể được biểu diễn bằng loại số nguyên không dấu kết quả là giảm modulo số lớn hơn giá trị lớn nhất có thể được biểu thị bằng kiểu kết quả. "Cụm từ này không bị giới hạn đối với tràn của giới hạn trên của loại, và áp dụng như nhau cho các giá trị quá thấp để được biểu diễn.


84
2017-08-28 14:06



Cảm ơn bạn! Bây giờ tôi thấy sự giải thích mà tôi đã mất tích. Tôi nghĩ rằng họ có thể đã chọn một từ ngữ rõ ràng hơn mặc dù. - jbcreix
Bây giờ tôi cảm thấy tốt hơn rất nhiều, biết rằng nếu bất kỳ phần bổ sung nào chưa được chuyển sẽ chuyển thành 0 và gây ra tình trạng lộn xộn, nó sẽ là vì uint luôn được dự định biểu diễn toán học nhẫn của các số nguyên 0 xuyên qua UINT_MAX, với các phép toán của phép cộng và phép nhân UINT_MAX+1và không phải vì tràn. Tuy nhiên, nó đặt ra câu hỏi về lý do tại sao, nếu các vòng là kiểu dữ liệu cơ bản, ngôn ngữ không cung cấp hỗ trợ chung cho các vòng kích thước khác. - Theodore Murdock
@ TheodoreMurdock Tôi nghĩ câu trả lời cho câu hỏi đó rất đơn giản. Theo như tôi có thể nói, thực tế rằng đó là một vòng là một hệ quả, không phải là một nguyên nhân. Yêu cầu thực sự là các kiểu unsigned phải có tất cả các bit của chúng tham gia vào biểu diễn giá trị. Hành vi giống như vòng chảy tự nhiên từ đó. Nếu bạn muốn hành vi như vậy từ các loại khác, sau đó làm số học của bạn theo sau bằng cách áp dụng các mô-đun cần thiết; sử dụng toán tử cơ bản. - underscore_d
@underscore_d Tất nhiên ... rõ ràng là tại sao họ đưa ra quyết định thiết kế. Thật thú vị khi họ viết spec về "không có số học trên / underflow bởi vì kiểu dữ liệu được chỉ định là vòng", như thể lựa chọn thiết kế này có nghĩa là các lập trình viên không phải cẩn thận tránh hơn và dưới -hoặc có chương trình của họ thất bại một cách ngoạn mục. - Theodore Murdock


Khi bạn làm việc với chưa ký các loại, số học mô-đun (còn được biết là "quấn quanh" hành vi) đang diễn ra. Để hiểu điều này số học mô-đun, chỉ cần nhìn vào những đồng hồ này:

enter image description here 

9 + 4 = 1 (13 mod 12), theo hướng khác, đó là: 1 - 4 = 9 (-3 mod 12). Cùng một nguyên tắc được áp dụng trong khi làm việc với các loại unsigned. Nếu loại kết quả Là unsigned, sau đó mô đun số học diễn ra.


Bây giờ, hãy xem các hoạt động sau lưu trữ kết quả dưới dạng unsigned int:

unsigned int five = 5, seven = 7;
unsigned int a = five - seven;      // a = (-2 % 2^32) = 4294967294 

int one = 1, six = 6;
unsigned int b = one - six;         // b = (-5 % 2^32) = 4294967291

Khi bạn muốn đảm bảo rằng kết quả là signed, sau đó lưu nó vào signedbiến hoặc truyền nó đến signed. Khi bạn muốn nhận được sự khác biệt giữa các số và đảm bảo rằng số học mô-đun sẽ không được áp dụng, thì bạn nên cân nhắc sử dụng abs() hàm được xác định trong stdlib.h:

int c = five - seven;       // c = -2
int d = abs(five - seven);  // d =  2

Hãy rất cẩn thận, đặc biệt là khi viết điều kiện, bởi vì:

if (abs(five - seven) < seven)  // = if (2 < 7)
    // ...

if (five - seven < -1)          // = if (-2 < -1)
    // ...

if (one - six < 1)              // = if (-5 < 1)
    // ...

if ((int)(five - seven) < 1)    // = if (-2 < 1)
    // ...

nhưng

if (five - seven < 1)   // = if ((unsigned int)-2 < 1) = if (4294967294 < 1)
    // ...

if (one - six < five)   // = if ((unsigned int)-5 < 5) = if (4294967291 < 5)
    // ...

96
2018-02-22 17:56



Đẹp với đồng hồ, mặc dù bằng chứng sẽ làm cho câu trả lời đúng. Tiền đề của câu hỏi đã bao gồm xác nhận rằng tất cả điều này có thể đúng. - Lightness Races in Orbit
@LightnessRacesinOrbit: Cảm ơn bạn. Tôi đã viết nó bởi vì tôi nghĩ rằng ai đó có thể thấy nó rất hữu ích. Tôi đồng ý, rằng đó không phải là câu trả lời hoàn chỉnh. - LihO
Dòng int d = abs(five - seven); là không tốt. Đầu tiên five - seven được tính toán: quảng cáo sẽ loại bỏ các loại toán hạng như unsigned int, kết quả được tính toán modulo (UINT_MAX+1)và đánh giá UINT_MAX-1. Sau đó, giá trị này là thông số thực tế abs, đó là tin xấu. abs(int) gây ra hành vi không xác định chuyển đối số, vì nó không nằm trong phạm vi và abs(long long) có thể giữ giá trị, nhưng hành vi không xác định xảy ra khi giá trị trả về bị ép buộc int để khởi tạo d. - Ben Voigt
@ LihO: Toán tử duy nhất trong C ++ có ngữ cảnh nhạy cảm và hoạt động khác nhau tùy thuộc vào cách kết quả của nó được sử dụng là toán tử chuyển đổi tùy chỉnh operator T(). Việc thêm vào trong hai biểu thức chúng ta đang thảo luận được thực hiện theo kiểu unsigned int, dựa trên các loại toán hạng. Kết quả của việc bổ sung là unsigned int. Sau đó, kết quả đó được chuyển đổi hoàn toàn thành loại được yêu cầu trong ngữ cảnh, một chuyển đổi không thành công do giá trị không thể biểu diễn trong loại mới. - Ben Voigt
@ LihO: Nó có thể giúp nghĩ về double x = 2/3; so với double y = 2.0/3; - Ben Voigt


Vâng, giải thích đầu tiên là chính xác. Tuy nhiên, lý do của bạn về "ngữ nghĩa đã ký" trong ngữ cảnh này là sai.

Một lần nữa, giải thích đầu tiên của bạn là chính xác. Số học chưa được ký theo các quy tắc của số học modulo, có nghĩa là 0x0000 - 0x0001 đánh giá 0xFFFF cho các loại không dấu 32 bit.

Tuy nhiên, giải thích thứ hai (dựa trên "ngữ nghĩa đã ký") cũng được yêu cầu để tạo ra cùng một kết quả. I E. ngay cả khi bạn đánh giá 0 - 1 trong miền của loại đã ký và có được -1 là kết quả trung gian, điều này -1 vẫn được yêu cầu để sản xuất 0xFFFF khi sau đó nó được chuyển thành kiểu unsigned. Ngay cả khi một số nền tảng sử dụng một đại diện kỳ ​​lạ cho các số nguyên đã ký (bổ sung 1, độ lớn đã ký), nền tảng này vẫn được yêu cầu áp dụng các quy tắc của số học modulo khi chuyển đổi các giá trị nguyên đã ký thành các số chưa ký.

Ví dụ, đánh giá này

signed int a = 0, b = 1;
unsigned int c = a - b;

vẫn được đảm bảo để sản xuất UINT_MAX trong c, ngay cả khi nền tảng đang sử dụng một biểu diễn kỳ lạ cho các số nguyên đã ký.


3
2018-02-22 18:22



Tôi nghĩ rằng bạn có nghĩa là 16 bit unsigned loại, không 32 bit. - xioxox


Với số lượng loại chưa ký unsigned int hoặc lớn hơn, trong trường hợp không có chuyển đổi loại, a-b được định nghĩa là sinh số chưa ký, khi được thêm vào b, sẽ mang lại a. Việc chuyển đổi một số âm thành unsigned được định nghĩa là sinh số mà khi được thêm vào số gốc đã ký, sẽ có giá trị bằng 0 (vì vậy chuyển đổi -5 thành unsigned sẽ mang lại giá trị, khi được thêm vào 5, sẽ mang lại 0) .

Lưu ý rằng số chưa ký nhỏ hơn unsigned int có thể được thăng cấp để nhập int trước phép trừ, hành vi của a-b sẽ phụ thuộc vào kích thước của int.


2
2018-01-09 17:22