Tại sao 'd / = d' không ném phép chia cho 0 ngoại lệ khi d == 0?


81

Tôi không hiểu tại sao tôi không nhận được một phép chia cho không ngoại lệ:

int d = 0;
d /= d;

Tôi mong đợi nhận được một phép chia cho không ngoại lệ nhưng thay vào đó d == 1.

Tại sao không d /= dném phép chia cho số không ngoại lệ khi d == 0?


25
Đó là hành vi không xác định.
LF

51
Không có cái gọi là phép chia cho số không ngoại lệ.
πάντα ῥεῖ

15
Để làm rõ một số nhận xét: khi bạn thấy thông báo về "ngoại lệ chia cho 0", đó là hệ điều hành cho bạn biết rằng đã xảy ra sự cố. Nó không phải là một ngoại lệ C ++. Trong C ++, các ngoại lệ được ném ra bởi một throwcâu lệnh. Không có gì khác (trừ khi bạn đang ở trong vùng đất có hành vi không xác định).
Pete Becker

9
Không có cái gọi là " ngoại lệ chia cho 0. " trong C ++.
Algirdas Preidžius

6
@ user11659763 "Đó là lý do tại sao nó là một hành vi không xác định: nó hoàn toàn phụ thuộc vào mục tiêu." - Đó không phải là những gì không xác định phương tiện hành vi ở tất cả ; những gì bạn đang mô tả là hành vi do triển khai xác định . Hành vi không xác định là một tuyên bố mạnh mẽ hơn nhiều.
marcelm

Câu trả lời:


108

C ++ không có Ngoại lệ "Division by Zero" để bắt. Hành vi bạn đang quan sát là kết quả của việc tối ưu hóa Trình biên dịch:

  1. Trình biên dịch giả định Hành vi không xác định không xảy ra
  2. Chia theo 0 trong C ++ là hành vi không xác định
  3. Do đó, mã có thể gây ra Division by Zero được cho là không làm như vậy.
    • Và, mã phải gây ra Division by Zero được cho là sẽ không bao giờ xảy ra
  4. Do đó, trình biên dịch suy luận rằng vì Hành vi không xác định không xảy ra, nên các điều kiện cho Hành vi không xác định trong mã này ( d == 0) không được xảy ra
  5. Do đó, d / dphải luôn luôn bằng 1.

Tuy nhiên...

Chúng tôi có thể buộc trình biên dịch kích hoạt phép chia "thực" cho 0 bằng một chỉnh sửa nhỏ đối với mã của bạn.

volatile int d = 0;
d /= d; //What happens?

Vì vậy, bây giờ câu hỏi vẫn còn: bây giờ về cơ bản chúng tôi đã buộc trình biên dịch cho phép điều này xảy ra, điều gì sẽ xảy ra? Đó là hành vi không xác định — nhưng bây giờ chúng tôi đã ngăn trình biên dịch tối ưu hóa xung quanh hành vi không xác định này.

Hầu hết, nó phụ thuộc vào môi trường mục tiêu. Điều này sẽ không kích hoạt một ngoại lệ phần mềm, nhưng nó có thể (tùy thuộc vào CPU mục tiêu) kích hoạt một Ngoại lệ phần cứng (một Số nguyên-Chia-bằng-0), không thể bị bắt theo cách truyền thống, một ngoại lệ phần mềm có thể bị bắt. Đây chắc chắn là trường hợp của CPU x86 và hầu hết các kiến ​​trúc khác (nhưng không phải tất cả!).

Tuy nhiên, có những phương pháp xử lý ngoại lệ phần cứng (nếu nó xảy ra) thay vì chỉ để chương trình gặp sự cố: hãy xem bài đăng này để biết một số phương pháp có thể áp dụng: Bắt ngoại lệ: chia cho không . Lưu ý rằng chúng khác nhau giữa các trình biên dịch.


25
@Adrian Cả hai đều hoàn toàn ổn vì hành vi không được xác định. Theo nghĩa đen, bất cứ điều gì là OK.
Jesper Juhl

9
"Chia theo số không trong C ++ là hành vi không xác định" -> lưu ý rằng trình biên dịch không thể thực hiện tối ưu hóa này cho các kiểu dấu phẩy động theo IEE754. Nó phải đặt d thành NaN.
Bathsheba

6
@RichardHodges trình biên dịch hoạt động theo IEEE754 không được phép thực hiện tối ưu hóa kép: NaN phải được tạo.
Bathsheba

2
@formerlyknownas: Vấn đề không phải là "tối ưu hóa UB" - vẫn là trường hợp "bất cứ điều gì có thể xảy ra"; chỉ là việc sản xuất 1là một thứ hoàn toàn hợp lệ. Nhận được 14684554 phải là do trình biên dịch tối ưu hóa hơn nữa - nó tuyên truyền d==0điều kiện ban đầu và do đó có thể kết luận không chỉ "đây là 1 hoặc UB" mà trên thực tế "đây là UB, kỳ". Do đó, nó thậm chí không bận tâm đến việc tạo ra mã tải hằng số 1.
hmakholm còn lại Monica

1
Mọi người luôn đề xuất tính dễ bay hơi để ngăn chặn việc tối ưu hóa, nhưng điều gì tạo nên việc đọc hoặc ghi biến động là do việc triển khai xác định.
philipxy

38

Chỉ để bổ sung cho các câu trả lời khác, thực tế là phép chia cho 0 là hành vi không xác định có nghĩa là trình biên dịch có thể tự do làm bất cứ điều gì trong các trường hợp xảy ra:

  • Trình biên dịch có thể giả định điều đó 0 / 0 == 1và tối ưu hóa cho phù hợp. Đó là hiệu quả những gì nó dường như đã làm ở đây.
  • Nếu muốn, trình biên dịch cũng có thể giả định điều đó 0 / 0 == 42và đặt thành dgiá trị đó.
  • Trình biên dịch cũng có thể quyết định rằng giá trị của dlà không xác định, và do đó, để biến không được khởi tạo, để giá trị của nó sẽ là bất cứ điều gì đã xảy ra trước đó được ghi vào bộ nhớ được cấp phát cho nó. Một số giá trị không mong muốn được quan sát thấy trên các trình biên dịch khác trong các nhận xét có thể do những trình biên dịch đó làm điều gì đó như thế này.
  • Trình biên dịch cũng có thể quyết định hủy bỏ chương trình hoặc đưa ra một ngoại lệ bất cứ khi nào xảy ra phép chia cho số 0. Vì đối với chương trình này, trình biên dịch có thể xác định rằng điều này sẽ luôn xảy ra, nó có thể chỉ phát ra mã để nâng cao ngoại lệ (hoặc hủy bỏ hoàn toàn việc thực thi) và coi phần còn lại của hàm là mã không thể truy cập.
  • Thay vì đưa ra một ngoại lệ khi phép chia cho số 0 xảy ra, trình biên dịch cũng có thể chọn dừng chương trình và bắt đầu một trò chơi Solitaire. Điều đó cũng thuộc về "hành vi không xác định".
  • Về nguyên tắc, trình biên dịch thậm chí có thể đưa ra mã khiến máy tính phát nổ bất cứ khi nào xảy ra phép chia cho số 0. Không có gì trong tiêu chuẩn C ++ cấm điều này. (Đối với một số loại ứng dụng, chẳng hạn như bộ điều khiển bay tên lửa, đây thậm chí có thể được coi là một tính năng an toàn đáng mong đợi!)
  • Hơn nữa, tiêu chuẩn cho phép rõ ràng hành vi không xác định để "du hành thời gian" , do đó trình biên dịch cũng có thể thực hiện bất kỳ điều nào ở trên (hoặc bất kỳ điều gì khác) trước khi phép chia cho 0 xảy ra. Về cơ bản, tiêu chuẩn cho phép trình biên dịch tự do sắp xếp lại các hoạt động miễn là hành vi có thể quan sát được của chương trình không bị thay đổi - nhưng ngay cả yêu cầu cuối cùng đó cũng được miễn trừ rõ ràng nếu việc thực thi chương trình sẽ dẫn đến hành vi không xác định. Vì vậy, trên thực tế, toàn bộ hành vi của bất kỳ thực thi chương trình nào, vào một thời điểm nào đó, sẽ kích hoạt hành vi không xác định là không xác định!
  • Như một hệ quả của điều trên, trình biên dịch cũng có thể đơn giản giả định rằng hành vi không xác định không xảy ra , vì một hành vi được phép đối với một chương trình sẽ hoạt động theo cách không xác định trên một số đầu vào là nó chỉ hoạt động như thể đầu vào là một cái gì đó khác . Có nghĩa là, ngay cả khi giá trị ban đầu của dkhông được biết tại thời điểm biên dịch, trình biên dịch vẫn có thể cho rằng nó không bao giờ bằng 0 và tối ưu hóa mã cho phù hợp. Trong trường hợp cụ thể của mã OP, điều này thực sự không thể phân biệt được với trình biên dịch chỉ cần giả sử rằng 0 / 0 == 1, nhưng trình biên dịch cũng có thể, ví dụ, giả sử rằng lệnh puts()in if (d == 0) puts("About to divide by zero!"); d /= d;không bao giờ được thực thi!

29

Hành vi của phép chia số nguyên cho 0 không được xác định theo tiêu chuẩn C ++. Người ta không cần phải ném một ngoại lệ.

(Phép chia dấu chấm động cho số 0 cũng không được xác định nhưng IEEE754 xác định nó.)

Trình biên dịch của bạn đang tối ưu hóa d /= dhiệu quả d = 1, đó là một lựa chọn hợp lý để thực hiện. Nó được phép thực hiện tối ưu hóa này vì nó được phép giả sử không có hành vi không xác định trong mã của bạn - điều đó dkhông thể là 0.


3
Điều quan trọng là phải cực kỳ rõ ràng, rằng điều gì đó cũng có thể xảy ra, IOW hành vi này không thể dựa vào.
hyde

2
Khi bạn nói điều hợp lý khi trình biên dịch giả định " dkhông thể là 0", bạn cũng giả định rằng trình biên dịch không nhìn thấy dòng: int d = 0;?? :)
Adrian Mole

6
Trình biên dịch thấy nó, nhưng có lẽ không quan tâm. Độ phức tạp mã bổ sung được yêu cầu trong trình biên dịch phức tạp vốn đã điên rồ cho một trường hợp cạnh như thế này có lẽ không đáng.
user4581301

1
@ user4581301 Kết hợp cả hai cho phép nó phát hiện một nhánh bị nhiễm độc, cho phép nó cắt tỉa nhiều mã hơn. Vì vậy, nó sẽ hữu ích.
Deduplicator

3
Vì vậy, nếu bạn đã viết "int d = 0; if (d == 0) printf (" d = zero \ n "); d / = d;", ​​trình biên dịch cũng có thể loại bỏ printf.
gnasher729

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.