Cảnh báo C ++: chia đôi cho không


98

Trường hợp 1:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0.0)<<std::endl;
}

Nó biên dịch mà không có bất kỳ cảnh báo và bản in nào inf. OK, C ++ có thể xử lý phép chia cho 0, ( xem trực tiếp ).

Nhưng,

Trường hợp 2:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0)<<std::endl;
}

Trình biên dịch đưa ra cảnh báo sau ( xem trực tiếp ):

warning: division by zero [-Wdiv-by-zero]
     std::cout<<(d/0)<<std::endl;

Tại sao trình biên dịch đưa ra cảnh báo trong trường hợp thứ hai?

0 != 0.0?

Biên tập:

#include <iostream>

int main()
{
    if(0 == 0.0)
        std::cout<<"Same"<<std::endl;
    else
        std::cout<<"Not same"<<std::endl;
}

đầu ra:

Same

9
Tôi giả sử nó nhận số 0 làm số nguyên trong trường hợp thứ hai và đưa ra cảnh báo, ngay cả khi phép tính sẽ được thực hiện bằng cách sử dụng double sau này (mà tôi nghĩ nên là hành vi khi d là một đôi).
Qubit

10
Đó thực sự là một vấn đề về QoI. Không phải cảnh báo hoặc thiếu cảnh báo là điều gì đó bắt buộc bởi chính tiêu chuẩn C ++. Bạn có đang sử dụng GCC không?
người kể chuyện - Unslander Monica


5
@StoryTeller QoI là gì? vi.wikipedia.org/wiki/QoI ?
user202729,

5
Liên quan đến câu hỏi cuối cùng của bạn, "0 có giống với 0,0 không?" Câu trả lời là các giá trị giống nhau, nhưng như bạn đã phát hiện ra, điều đó không có nghĩa là chúng giống hệt nhau. Các loại khác nhau! Cũng giống như 'A' là không giống với 65.
Ông Lister

Câu trả lời:


108

Phép chia dấu chấm động cho không được IEEE xác định rõ và cho ra vô cùng (dương hoặc âm theo giá trị của tử số (hoặc NaNcho ± 0) ).

Đối với số nguyên, không có cách nào để biểu diễn vô hạn và ngôn ngữ xác định hoạt động có hành vi không xác định, vì vậy trình biên dịch cố gắng hướng dẫn bạn rõ ràng khỏi đường dẫn đó.

Tuy nhiên trong trường hợp này, vì tử số là a double, số chia ( 0) cũng nên được thăng cấp thành nhân đôi và không có lý do gì để đưa ra cảnh báo ở đây trong khi không đưa ra cảnh báo 0.0vì vậy tôi nghĩ đây là một lỗi trình biên dịch.


8
Tuy nhiên, cả hai đều là sự phân chia dấu chấm động. Trong d/0, 0được chuyển đổi thành loại d.

43
Lưu ý rằng C ++ không cần phải sử dụng IEEE 754 (mặc dù tôi chưa bao giờ thấy một trình biên dịch sử dụng một tiêu chuẩn khác nhau) ..
Yksisarvinen

1
@hvd, điểm tốt, nó trường hợp đó này trông giống như một trình biên dịch lỗi
Motti

14
Tôi đồng ý rằng nó sẽ cảnh báo trong cả hai trường hợp, hoặc không cảnh báo trong cả hai trường hợp (tùy thuộc vào cách xử lý của trình biên dịch đối với phép chia động cho số 0 là gì)
MM

8
Khá chắc chắn rằng phép chia dấu phẩy động cho 0 cũng là UB - chỉ là GCC thực hiện nó theo IEEE 754. Tuy nhiên, họ không phải làm điều đó.
Martin Bonner hỗ trợ Monica

42

Trong C ++ tiêu chuẩn, cả hai trường hợp đều là hành vi không xác định . Bất cứ điều gì có thể xảy ra, bao gồm cả việc định dạng ổ cứng của bạn. Bạn không nên trông đợi hoặc dựa vào "return inf. Ok" hoặc bất kỳ hành vi nào khác.

Trình biên dịch dường như quyết định đưa ra cảnh báo trong một trường hợp chứ không phải trường hợp khác, nhưng điều này không có nghĩa là một mã thì được và một mã thì không. Nó chỉ là một sai lầm của thế hệ cảnh báo của trình biên dịch.

Từ tiêu chuẩn C ++ 17 [expr.mul] / 4:

/Toán tử nhị phân sinh ra thương số và %toán tử nhị phân sinh ra phần còn lại từ phép chia biểu thức đầu tiên cho biểu thức thứ hai. Nếu toán hạng thứ hai của /hoặc %bằng 0 thì hành vi không được xác định.


21
Không đúng, trong số học dấu phẩy động, phép chia cho 0 được xác định rõ.
Motti

9
@Motti - Nếu một người tự giới hạn mình trong tiêu chuẩn C ++, thì không có gì đảm bảo như vậy. Thành thật mà nói, phạm vi của câu hỏi này không được xác định rõ ràng.
người kể chuyện - Unslander Monica

9
@StoryTeller Tôi khá chắc chắn (mặc dù tôi chưa xem tài liệu chuẩn về việc này) rằng nếu std::numeric_limits<T>::is_iec559true, thì phép chia cho 0 cho Tkhông phải là UB (và trên hầu hết các nền tảng, nó truecho doublefloat, mặc dù để có thể di động, bạn sẽ cần phải kiểm tra rõ ràng điều đó bằng dấu ifhoặc if constexpr).
Daniel H

6
@DanielH - "Hợp lý" thực ra khá chủ quan. Nếu câu hỏi này được gắn thẻ luật sư ngôn ngữ thì sẽ có một tập hợp hoàn toàn khác (nhỏ hơn nhiều) các giả định hợp lý.
người kể chuyện - Unslander Monica

5
@MM Hãy nhớ rằng đó chính xác là những gì bạn đã nói, nhưng không có gì hơn: không xác định không có nghĩa là không có yêu cầu nào được áp đặt, nó có nghĩa là không có yêu cầu nào được áp đặt theo tiêu chuẩn . Tôi sẽ tranh luận rằng trong trường hợp này, thực tế là một triển khai được định nghĩa is_iec559truecó nghĩa là việc triển khai đang ghi lại hành vi mà tiêu chuẩn không xác định. Chỉ vì đây là một trường hợp mà tài liệu của việc triển khai có thể được đọc theo chương trình. Thậm chí không phải là duy nhất: điều tương tự cũng áp dụng is_modulocho các kiểu số nguyên có dấu.

12

Dự đoán tốt nhất của tôi để trả lời câu hỏi cụ thể này sẽ là trình biên dịch phát ra cảnh báo trước khi thực hiện chuyển đổi intthành double.

Vì vậy, các bước sẽ như sau:

  1. Phân tích cú pháp biểu thức
  2. Toán tử số học /(T, T2) , nơi T=double, T2=int.
  3. Kiểm tra đó std::is_integral<T2>::valuetrueb == 0- điều này kích hoạt cảnh báo.
  4. Phát ra cảnh báo
  5. Thực hiện chuyển đổi ngầm T2thànhdouble
  6. Thực hiện phân chia được xác định rõ ràng (vì trình biên dịch quyết định sử dụng IEEE 754).

Tất nhiên đây là suy đoán và dựa trên các thông số kỹ thuật do trình biên dịch xác định. Từ quan điểm tiêu chuẩn, chúng tôi đang giải quyết các Hành vi Không xác định có thể xảy ra.


Xin lưu ý rằng đây là hành vi được mong đợi theo tài liệu GCC
(btw. Có vẻ như cờ này không thể được sử dụng rõ ràng trong GCC 8.1)

-Wdiv-by-zero
Cảnh báo về phép chia số nguyên theo thời gian biên dịch cho không. Đây là mặc định. Để ngăn các thông báo cảnh báo, hãy sử dụng -Wno-div-by-zero. Việc chia dấu phẩy động cho 0 không được cảnh báo vì nó có thể là một cách hợp pháp để lấy số vô hạn và NaN.


2
Đây không phải là cách trình biên dịch C ++ hoạt động. Trình biên dịch phải thực hiện giải quyết quá tải /để biết rằng đó là sự phân chia. Nếu bên tay trái là một Foođối tượng, và có một operator/(Foo, int), thì nó thậm chí có thể không được phân chia. Trình biên dịch chỉ biết nó phân chia khi nó đã chọn built-in / (double, double)bằng cách sử dụng chuyển đổi ngầm định của phía bên phải. Nhưng điều đó có nghĩa là nó KHÔNG chia cho int(0), nó đang chia cho double(0).
MSalters

@MSalters Vui lòng xem điều này. Kiến thức của tôi về C ++ còn hạn chế, nhưng theo tài liệu tham khảo operator /(double, int)thì chắc chắn có thể chấp nhận được. Sau đó, nó cho biết chuyển đổi được thực hiện trước bất kỳ hành động nào khác, nhưng GCC có thể kiểm tra nhanh xem có phải T2là kiểu số nguyên hay không b == 0và phát ra cảnh báo nếu có. Không chắc liệu đó có tuân thủ tiêu chuẩn hoàn toàn hay không, nhưng các trình biên dịch có toàn quyền tự do trong việc xác định cảnh báo và khi nào chúng nên được kích hoạt.
Yksisarvinen

2
Chúng tôi đang nói về toán tử tích hợp ở đây. Đó là một điều buồn cười. Nó không thực sự là một chức năng, vì vậy bạn không thể lấy địa chỉ của nó. Do đó bạn không thể xác định xem có operator/(double,int)thực sự tồn tại hay không. Ví dụ, trình biên dịch có thể quyết định tối ưu hóa a/bcho hằng số bbằng cách thay thế nó bằng a * (1/b). Tất nhiên, điều đó có nghĩa là bạn không còn gọi operator/(double,double)trong thời gian chạy nữa mà nhanh hơn operator*(double,double). Nhưng nó bây giờ tôi ưu hoa mà các chuyến đi qua 1/0, hằng nó sẽ phải ăn đếnoperator*
MSalters

@MSalters chung nổi phân chia điểm không thể được thay thế bằng nhân, có lẽ ngoại trừ trường hợp đặc biệt, chẳng hạn như 2.
user202729

2
@ user202729: GCC thực hiện ngay cả khi chia số nguyên . Hãy để chìm trong một lúc. GCC thay thế phép chia số nguyên bằng phép nhân số nguyên. Vâng, đó là có thể, bởi vì GCC biết nó đang hoạt động trên một chiếc nhẫn (số modulo 2 ^ N)
MSalters

9

Tôi sẽ không đi vào sự cố UB / không UB trong câu trả lời này.

Tôi chỉ muốn chỉ ra điều đó 00.0 khác biệt mặc dù 0 == 0.0đánh giá là đúng. 0là một intnghĩa đen và 0.0là một doublenghĩa đen.

Tuy nhiên trong trường hợp này, kết quả cuối cùng là giống nhau: d/0là phép chia dấu phẩy động vì dlà đôi và do đó 0được chuyển đổi ngầm thành gấp đôi.


5
Tôi không thấy điều này có liên quan như thế nào, vì các chuyển đổi số học thông thường chỉ định rằng chia a doublecho một intnghĩa là số intđược chuyển đổi thành double và nó được chỉ định trong tiêu chuẩn 0chuyển đổi thành 0.0 ( chuyển đổi fpint / 2)
MM

@MM OP muốn biết có 0giống với0.0
bolov

2
Câu hỏi nói "Là 0 != 0.0?". OP không bao giờ hỏi liệu chúng có "giống nhau" hay không. Ngoài ra, đối với tôi dường như mục đích của câu hỏi là liên quan đến việc liệu d/0có thể cư xử khác vớid/0.0
MM

2
@MM - OP đã hỏi . Họ không thực sự hiển thị SO netiquette đàng hoàng với những chỉnh sửa hằng số đó.
người kể chuyện - Unslander Monica

7

Tôi cho rằng foo/0foo/0.0không giống nhau. Cụ thể, kết quả của phép chia đầu tiên (phép chia số nguyên hoặc phép chia dấu phẩy động) phụ thuộc nhiều vào kiểu của foo, trong khi điều này không đúng với phép chia thứ hai (nó sẽ luôn là phép chia dấu phẩy động).

Cho dù bất kỳ cái nào trong hai cái là UB đều không liên quan. Trích dẫn tiêu chuẩn:

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).

(Tôi nhấn mạnh)

Hãy xem xét cảnh báo " gợi ý dấu ngoặc quanh phép gán được sử dụng làm giá trị sự thật ": Cách để cho trình biên dịch biết rằng bạn thực sự muốn sử dụng kết quả của một phép gán là rõ ràng và thêm dấu ngoặc quanh bài tập. Câu lệnh kết quả cũng có tác dụng tương tự, nhưng nó cho trình biên dịch biết bạn đang làm gì. Điều tương tự cũng có thể nói về foo/0.0: Vì bạn đang nói rõ ràng với trình biên dịch "Đây là phép phân chia dấu chấm động" bằng cách sử dụng 0.0thay vì 0, trình biên dịch tin tưởng bạn và sẽ không đưa ra cảnh báo.


1
Cả hai đều phải trải qua các phép chuyển đổi số học thông thường để đưa chúng về một loại chung, điều này sẽ để lại phép chia dấu phẩy động cho cả hai trường hợp.
Shafik Yaghmour,

@ShafikYaghmour Bạn đã bỏ sót ý trong câu trả lời. Lưu ý rằng tôi chưa bao giờ đề cập đến loại là gì foo. Đó là cố ý. Khẳng định của bạn chỉ đúng trong trường hợp đó foolà kiểu dấu phẩy động.
Cássio Renan

Tôi đã không, trình biên dịch có thông tin kiểu và hiểu chuyển đổi, có lẽ một trình phân tích tĩnh dựa trên văn bản có thể bị những thứ như vậy bắt gặp nhưng trình biên dịch thì không.
Shafik Yaghmour

Quan điểm của tôi là có, trình biên dịch biết các chuyển đổi số học thông thường, nhưng nó chọn không đưa ra cảnh báo khi lập trình viên đang rõ ràng. Toàn bộ vấn đề là đây có lẽ không phải là lỗi, mà thay vào đó nó là hành vi có chủ đích.
Cássio Renan

Sau đó, tài liệu tôi đã chỉ đến là không chính xác, vì cả hai trường hợp đều là phép chia dấu phẩy động. Vì vậy, hoặc tài liệu sai hoặc chẩn đoán có lỗi.
Shafik Yaghmour,

4

Điều này trông giống như một lỗi gcc, tài liệu cho -Wno-div-by-zero biết rõ ràng :

Không cảnh báo về phép chia số nguyên thời gian biên dịch cho không. Phép chia dấu phẩy động cho 0 không được cảnh báo vì nó có thể là một cách hợp pháp để lấy số vô hạn và NaN.

và sau các chuyển đổi số học thông thường được đề cập trong [expr.arith.conv], cả hai toán hạng sẽ là gấp đôi :

Nhiều toán tử nhị phân mong đợi các toán hạng của kiểu số học hoặc kiểu liệt kê gây ra chuyển đổi và mang lại các kiểu kết quả theo cách tương tự. Mục đích là mang lại một kiểu chung, cũng là kiểu kết quả. Mẫu này được gọi là các chuyển đổi số học thông thường, được định nghĩa như sau:

...

- Ngược lại, nếu một trong hai toán hạng là gấp đôi thì toán hạng kia sẽ được chuyển thành kép.

[expr.mul] :

Các toán hạng của * và / sẽ có kiểu liệt kê số học hoặc không ghi; các toán hạng% phải có kiểu liệt kê tích phân hoặc không tích phân. Các chuyển đổi số học thông thường được thực hiện trên các toán hạng và xác định loại kết quả.

Liên quan đến việc liệu phép chia dấu phẩy động cho 0 có phải là hành vi không xác định hay không và cách triển khai khác nhau đối phó với nó dường như là câu trả lời của tôi ở đây . TL; DR; Có vẻ như gcc tuân theo Phụ lục F wrt để chia dấu phẩy động cho 0, do đó, undefined không đóng một vai trò nào ở đây. Câu trả lời sẽ khác đối với tiếng kêu.


2

Phép chia dấu phẩy động cho số 0 hoạt động khác với phép chia số nguyên cho số 0.

Tiêu chuẩn dấu phẩy động IEEE phân biệt giữa + inf và -inf, trong khi số nguyên không thể lưu trữ vô hạn. Phép chia số nguyên cho kết quả bằng không là hành vi không xác định. Phép chia dấu phẩy động cho không được xác định bởi tiêu chuẩn dấu phẩy động và cho kết quả là + inf hoặc -inf.


2
Điều này đúng nhưng không rõ điều này có liên quan đến câu hỏi như thế nào, vì phép chia dấu phẩy động được thực hiện trong cả hai trường hợp . Không có phân chia số nguyên trong mã của OP.
Konrad Rudolph
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.