2009-11-24 23 views
8

Khi viết này:Toán tử có điều kiện nên đánh giá tất cả các đối số?

1: inline double f(double arg) { 
2: return arg == 0.0 ? 0.0 : 1./arg; 
3: } 
4: const double d = f(0.0); 

Các visual studio microsoft 2005 64-bit biên dịch đi kèm với

line 4: warning C4723: potential divide by 0 

Trong khi bạn và tôi có thể thấy rõ rằng một div-by-zero là bao giờ đi để xảy ra ...

Hay là nó?

+11

Hãy quan tâm, so sánh các đối số "gấp đôi" về bình đẳng. Ma thuật tà ác xảy ra ở đó ... – SadSido

+1

Không. Quy trình được xác định hoàn hảo. Đặc biệt, '0.0 == -0.0'. Do đó, đối với tất cả các tập hợp các giá trị mà '1/arg' được định nghĩa, chúng ta biết rằng' arg! = 0.0'. – MSalters

+1

@MSalters: Nhưng do lỗi làm tròn, arg có thể không phải là 0,0 (hoặc -0,0 cho vấn đề đó) khi bạn mong đợi nó. – jalf

Trả lời

4

Đó là một lỗi rõ ràng, ngoài sự nghi ngờ.

Mục đích của cảnh báo KHÔNG được cảnh báo về tất cả các bộ phận trong chương trình. Điều đó sẽ quá ồn ào trong bất kỳ chương trình hợp lý nào. Thay vào đó, mục đích là để cảnh báo bạn khi bạn cần kiểm tra một đối số. Trong trường hợp này, bạn đã kiểm tra đối số. Do đó, trình biên dịch nên đã lưu ý rằng, và đóng cửa.

Việc triển khai kỹ thuật tính năng như vậy được thực hiện bằng cách gắn nhãn các biến trong các nhánh mã với các thuộc tính nhất định. Một trong những thuộc tính phổ biến nhất là trạng thái ba "Is null". Trước chi nhánh, arg là biến bên ngoài và arg [[Isnull]] không xác định. Nhưng sau khi kiểm tra trên arg có hai chi nhánh. Trong chi nhánh đầu tiên arg [[Isnull]] là đúng sự thật. Trong nhánh thứ hai arg [[Isnull]] là sai.

Bây giờ, khi nói đến việc tạo cảnh báo con trỏ chia và không, thuộc tính [[IsNull] phải được kiểm tra. Nếu đúng, bạn có cảnh báo/lỗi nghiêm trọng. Nếu không biết, bạn nên tạo ra cảnh báo được hiển thị ở trên - một vấn đề tiềm ẩn, ngoài những gì trình biên dịch có thể chứng minh. Nhưng trong trường hợp này, thuộc tính [[isNull]] là Sai. Trình biên dịch bằng logic chính thức giống như con người, biết rằng không có rủi ro.

Nhưng làm cách nào để biết trình biên dịch đang sử dụng thuộc tính [[Isnull]] nội bộ? Nhớ lại đoạn đầu tiên: không có nó, nó sẽ phải cảnh báo luôn hoặc không bao giờ. Chúng ta biết nó cảnh báo đôi khi, ergo phải có một thuộc tính [[IsNull]].

+0

Đó là lý do vững chắc. Tôi có thể sống với điều đó - đặc biệt vì nó chứng tỏ cảm giác ruột của tôi. – xtofl

+0

Một lỗi Linux gần đây đã được gây ra bởi chính xác thuộc tính này. Trong mã vi phạm, một con trỏ đầu tiên bị bỏ qua (về cơ bản là 'int * member = & (foo-> bar);' và sau đó kiểm tra 'if (! Foo) return;'. Tuy nhiên, do sự dereference, 'foo [ [IsNull]] 'thuộc tính đã được đặt thành false và kiểm tra NULL được tối ưu hóa. – MSalters

11

Trình biên dịch không thể phân tích tĩnh tất cả các đường dẫn mã và xem xét tất cả các khả năng mọi lúc. Về mặt lý thuyết, phân tích đầy đủ về hành vi của chương trình chỉ bằng cách xem xét mã nguồn của nó có thể cung cấp một giải pháp để ngăn chặn vấn đề, điều không thể giải quyết được. Trình biên dịch có một bộ giới hạn các quy tắc phân tích tĩnh để phát hiện các quy tắc. Tiêu chuẩn C++ không yêu cầu trình biên dịch đưa ra các cảnh báo như vậy, vì vậy, không. Nó không phải là một lỗi. Nó giống như một tính năng không tồn tại.

+0

Bạn nói đúng. Nó không bao giờ có thể là một lỗi từ quan điểm chuẩn C++, vì nó liên quan đến cảnh báo. Tuy nhiên, nếu 'lỗi' này buộc tôi viết lại mã hoàn toàn hợp lệ (hoặc bao quanh nó bằng '# pragma'), từ quan điểm 'chương trình máy tính', đó là lỗi. – xtofl

+0

@xtofl: Từ khóa trong cảnh báo là "tiềm năng" –

+0

Đồng ý, nhưng nỗi đau quan trọng là chúng tôi cố gắng xử lý các cảnh báo dưới dạng lỗi và không thể thực hiện điều đó với cảnh báo 'dương tính giả'. – xtofl

7

Không, toán tử điều kiện không đánh giá cả hai đối số. Tuy nhiên, một phân chia tiềm năng-by-zero, nếu một trình biên dịch có thể phát hiện một điều như vậy, thường được báo cáo. Nó không phải là vô ích mà tiêu chuẩn chiếm ~ 2 trang để mô tả hành vi của nhà điều hành này.

Từ N-4411:

5,16 khai thác có điều kiện

biểu thức có điều kiện nhóm từ phải sang trái. Biểu thức đầu tiên là được chuyển đổi theo ngữ cảnh thành bool (Điều khoản 4). Nó được đánh giá và nếu nó là đúng, kết quả của biểu thức có điều kiện là giá trị của biểu thức thứ hai, nếu không thì biểu thức thứ ba là . Chỉ một trong các biểu thức thứ hai và thứ ba là được đánh giá là . Mỗi tính toán giá trị và hiệu ứng phụ được kết hợp với biểu thức đầu tiên được sắp xếp theo trình tự trước mỗi tính toán giá trị và tác dụng phụ được kết hợp với biểu thức thứ hai hoặc thứ ba .

Ngoài ra, lưu ý:

Ngược lại, nếu thứ hai và thứ ba toán hạng có các loại khác nhau, và hoặc đã (có thể cv-đủ điều kiện) lớp loại, một nỗ lực được thực hiện để chuyển đổi mỗi toán hạng đó thành loại của loại kia.

Ví dụ bạn đã trích dẫn có cùng loại cho cả biểu thức thứ hai và thứ ba - hãy yên tâm, chỉ lần đầu tiên được đánh giá.

+0

Tôi nghi ngờ điểm 3 thực sự nói rằng cả hai toán hạng sẽ được đánh giá. Tôi nghĩ rằng điều này khá quan tâm cho dù những thứ như 'std :: string s (" A "); const char * c = "B"; std :: string a = cond()? s: c; const char * b = cond()? s: c; 'nên biên dịch hay không (xác định loại kết quả của toán tử nên là gì: trình biên dịch kiểm tra cái gì có thể được truyền tới cái gì - trong trường hợp này là cái đầu tiên?: biên dịch, vì' const char * 'có thể được chuyển đổi hoàn toàn tới std :: string, nhưng thứ hai không biên dịch, vì kiểu kết quả của toán tử là 'std :: string', và điều này không thể được chuyển đổi thành' const char * ') – UncleBens

+0

Accpeted và edited. Tôi nên xác định điều đó. – dirkgently

3

mã cho bộ phận sẽ được tạo, do đó cảnh báo. nhưng chi nhánh sẽ không bao giờ được thực hiện khi arg là 0, vì vậy nó an toàn.

3

toán tử == cho số dấu phẩy động là không an toàn (nghĩa là bạn không thể tin tưởng số đó do các vấn đề làm tròn). Trong trường hợp cụ thể này, nó thực sự an toàn, vì vậy bạn có thể bỏ qua cảnh báo, nhưng trình biên dịch sẽ không thực hiện phân tích như vậy dựa trên một toán tử có kết quả không thể đoán trước được trong trường hợp chung.

3

Toán tử điều kiện không được đánh giá tất cả đối số. Nhưng tôi tin rằng bạn có thể mất arg gần bằng 0, vì vậy arg == 0.0 sẽ là false, nhưng 1./arg sẽ cho kết quả "chia cho số không". Vì vậy, tôi nghĩ rằng cảnh báo là hữu ích ở đây.

Nhân tiện, Visual C++ 2008 không đưa ra cảnh báo như vậy.

+0

Điều đó có ý nghĩa. Cảm ơn. – xtofl

+1

IEEE 754 đơn vị dấu chấm động trong C sẽ không ném chia cho số không. Kết quả chia cho 0 sẽ là + Infinity, -Infinity hoặc NaN –

+0

Kết quả có thể là + Infinity hoặc -Infinity cho các số phụ, nhưng tôi sẽ gọi là overflow chứ không phải phân chia bằng 0. – starblue

0

Ngoài các nhận xét khác: cảnh báo được tạo bởi trình biên dịch, nhánh chết được xóa bởi trình tối ưu hóa chạy sau - thậm chí có thể ở giai đoạn liên kết.

Vì vậy, không, đó không phải là lỗi. Cảnh báo là một dịch vụ bổ sung được cung cấp bởi trình biên dịch, không được bắt buộc theo tiêu chuẩn. Đó là một tác dụng phụ không may của kiến ​​trúc trình biên dịch/liên kết.

0

Bạn có thể tránh cảnh báo bằng cách sử dụng từ khóa __assume của Microsoft cụ thể. Tôi không chắc chắn nếu bạn có thể buộc nó với các nhà điều hành có điều kiện. Nếu không, chẳng hạn như

if (arg == 0.0){ 
    return 0.0; 
} 
else { 
__assume(arg != 0.0); 
    return 1./arg; 
} 

có thể đáng để chụp. Hoặc, tất nhiên, chỉ cần im lặng cảnh báo trong chức năng này, với #pragma thích hợp.

Các vấn đề liên quan