Chương trình hoạt động lạ trên các IDE trực tuyến


82

Tôi đã xem qua chương trình C ++ bên dưới ( nguồn ):

#include <iostream>
int main()
{
    for (int i = 0; i < 300; i++)
        std::cout << i << " " << i * 12345678 << std::endl;
}

Nó trông giống như một chương trình đơn giản và cung cấp đầu ra chính xác trên máy cục bộ của tôi, tức là một cái gì đó như:

0 0
1 12345678
2 24691356
...
297 -628300930
298 -615955252
299 -603609574

Tuy nhiên, trên các IDE trực tuyến như codechef , nó cho kết quả sau:

0 0
1 12345678
2 24691356
...
4167 -95167326
4168 -82821648
4169 -7047597

Tại sao forvòng lặp không kết thúc ở 300? Ngoài ra chương trình này luôn kết thúc vào 4169. Tại sao 4169và không phải một số giá trị khác?


45
Có thể là do tràn số nguyên có dấu có hành vi không xác định.
eerorika

17
Tôi biết tràn đã ký là UB nhưng đây là một thất bại ngoạn mục ...
Galik

45
Một bài học hay trong việc không giả định UB bị hạn chế
MMORPG

12
Tôi tò mò là tại sao bạn nghĩ rằng đầu ra đầu tiên là "đầu ra chính xác".
Các cuộc đua ánh sáng trong quỹ đạo

5
@LightnessRacesinOrbit - tốt, lời nhắc đó rất có giá trị!
davidbak

Câu trả lời:


108

Tôi sẽ giả định rằng các trình biên dịch trực tuyến sử dụng GCC hoặc trình biên dịch tương thích. Tất nhiên, bất kỳ trình biên dịch nào khác cũng được phép thực hiện tối ưu hóa tương tự, nhưng tài liệu GCC giải thích rõ những gì nó làm:

-faggressive-loop-optimizations

Tùy chọn này yêu cầu trình tối ưu hóa vòng lặp sử dụng các ràng buộc ngôn ngữ để lấy ra các giới hạn cho số lần lặp của một vòng lặp. Điều này giả định rằng mã vòng lặp không gọi hành vi không xác định bằng cách ví dụ như gây tràn số nguyên có dấu hoặc truy cập mảng ngoài giới hạn. Các giới hạn cho số lần lặp lại của một vòng lặp được sử dụng để hướng dẫn tối ưu hóa kiểm tra việc mở vòng lặp và bóc và thoát vòng lặp. Tùy chọn này được kích hoạt theo mặc định.

Tùy chọn này chỉ cho phép đưa ra các giả định dựa trên các trường hợp UB được chứng minh. Để tận dụng những giả định đó, có thể cần bật các tính năng tối ưu hóa khác, chẳng hạn như gấp liên tục.


Tràn số nguyên đã ký có hành vi không xác định. Trình tối ưu hóa đã có thể chứng minh rằng bất kỳ giá trị nào ilớn hơn 173 sẽ gây ra UB và bởi vì nó có thể giả định rằng không có UB, nó cũng có thể giả định rằng ikhông bao giờ lớn hơn 173. Sau đó, nó có thể chứng minh thêm rằng điều đó i < 300luôn đúng, và vì vậy điều kiện vòng lặp có thể được tối ưu hóa đi.

Tại sao lại là 4169 mà không phải một số giá trị khác?

Các trang web đó có thể giới hạn số lượng dòng xuất (hoặc ký tự hoặc byte) mà chúng hiển thị và tình cờ chia sẻ cùng một giới hạn.


3
Nếu trình biên dịch có thể chứng minh rằng sẽ có UB cho bất kỳ giá trị nào ilớn hơn 173, vậy tại sao nó không phát ra cảnh báo thay vì thực hiện tối ưu hóa vô nghĩa?
Jabberwocky

7
@MichaelWalz Nó phát ra một.
HolyBlackCat

3
@MichaelWalz: Loại bỏ các bài kiểm tra boolean dư thừa không phải là một cách tối ưu hóa vô nghĩa; nó là một trong những rất hữu ích. Gì sẽ được (chủ yếu) vô nghĩa được bổ sung thêm mã để vô hiệu hóa / undo tối ưu hóa sự hiện diện của mã nguồn bị hỏng.

25
@MichaelWalz: Trình biên dịch không thể phát hiện UB một cách đáng tin cậy như bạn đề xuất (mặc dù đôi khi nó có thể cảnh báo về một sự cố có thể xảy ra, như nó thực sự xảy ra ở đây). Thay vào đó, nó có thể tiến hành với mục đích tốt nhất với giả định rằng sẽ không có UB . Đó là hai điều có lẽ tinh tế nhưng trên thực tế lại khác nhau đáng kể. Nó không giống như trình biên dịch "aha! UB! Bây giờ tôi có thể bật như vậy và tối ưu hóa như vậy " - tối ưu hóa luôn ở đó. Nó làm những thứ như thế này mọi lúc . Nhưng, miễn là chương trình của bạn được viết chính xác, bạn sẽ không chứng kiến ​​bất kỳ thay đổi nào đối với ngữ nghĩa của nó.
Các cuộc đua ánh sáng trong quỹ đạo

20
Tương tự như vậy, nhà sản xuất cửa trước của ngôi nhà của bạn có thể đã quyết định rằng nó sẽ chắc chắn hơn nếu nó có một miếng kim loại được đặt một cách chiến thuật ở đâu đó ở giữa nó. Bạn sẽ không bao giờ nhận ra, trừ khi bạn đang đục lỗ trên cửa và do đó sử dụng sai cửa .
Các cuộc đua ánh sáng trong quỹ đạo

40

"Hành vi không xác định là không xác định." (c)

Một trình biên dịch được sử dụng trên codechef dường như sử dụng logic sau:

  1. Hành vi không xác định không thể xảy ra.
  2. i * 12345678tràn và kết quả là UB nếu i > 173(giả sử 32 bit ints).
  3. Như vậy, ikhông bao giờ có thể vượt quá 173.
  4. Như vậy i < 300là không cần thiết và có thể được thay thế bằng true.

Vòng lặp tự nó dường như là vô hạn. Rõ ràng, codechef chỉ dừng chương trình sau một khoảng thời gian cụ thể hoặc cắt bớt đầu ra.


11
@ArpanMangal Tôi vừa đếm các ký tự trong đầu ra, và có vẻ như vậy 2^16. Rõ ràng đó là một sự trùng hợp khi cả hai đều cắt ngắn đầu ra cho các 2^16ký tự.
HolyBlackCat

3
@ArpanMangal: quỷ mũi như 4169, và hiện đang tiệc tùng tại Ideone và codechef. UB là không xác định, nó có thể là bất cứ thứ gì, kể cả quỷ mũi. Nói một cách nghiêm túc, việc cố gắng phân tích UB là một việc lãng phí thời gian, hãy sử dụng thời gian đó để tìm ra cách ngăn nó xảy ra.
jmoreno 11/02/18

6
@jmoreno nó không phải là một sự lãng phí thời gian nếu bạn đang quan tâm đến việc biên dịch thiết kế
user253751

2
@jmoreno Mặc dù vậy, lần này có thể phân tích nó. Hiểu chính xác cách UB phá vỡ công cụ có thể hữu ích để kết luận trường hợp nào UB được chấp nhận, nếu có.
HolyBlackCat

4
@jmoreno Bất cứ điều gì vũ trụ làm đều đúng (theo định nghĩa), vậy mục đích của việc nghiên cứu thiên văn học là gì?
user253751

11

Bạn đang gọi hành vi không xác định có thể là ở lần lặp thứ 174 bên trong forvòng lặp của bạn vì intgiá trị tối đa có thể là biểu thức 2147483647chưa được 174 * 123456789đánh giá 2148147972là hành vi không xác định vì không có tràn số nguyên có dấu. Vì vậy, bạn đang quan sát các tác động của UB, đặc biệt là với trình biên dịch GCC với các cờ tối ưu hóa được đặt trong trường hợp của bạn. Có khả năng trình biên dịch đã cảnh báo bạn về điều này bằng cách đưa ra cảnh báo sau:

warning: iteration 174 invokes undefined behavior [-Waggressive-loop-optimizations]

Loại bỏ các -O2cờ tối ưu hóa ( ) để quan sát các kết quả khác nhau.


4
Cần lưu ý rằng hành vi không xác định có thể có hiệu ứng hồi tố - bởi vì UB sẽ xảy ra ở lần lặp 174, tiêu chuẩn thậm chí không yêu cầu 173 lần lặp đầu tiên của vòng lặp phải tiến hành như mong đợi!

@Hurkyl Thật vậy. Có một điều hơi nghịch lý là UB khiến toàn bộ chương trình, bao gồm cả các tuyên bố trước đó thể hiện UB.
Ron

12
@Ron: Nó không phải là nghịch lý. Chương trình có ngữ nghĩa được xác định rõ ràng hoặc không. Giai đoạn = Stage. Hãy nhớ rằng, mã C ++ không phải là một chuỗi các hướng dẫn được thực hiện; nó là một mô tả của một chương trình .
Các cuộc đua ánh sáng ở Orbit

@LightnessRacesinOrbit: Một chương trình có thể có ngữ nghĩa chưa được xác định một phần nhưng không phải là hoàn toàn không được xác định. Tiêu chuẩn C cố gắng áp dụng khái niệm đó cho cái mà nó gọi là "Bound UB", mặc dù ngôn ngữ mà nó sử dụng hơi mơ hồ. Việc cho phép các trình biên dịch tự do rộng rãi nhưng không giới hạn trong việc xử lý những thứ như tràn số nguyên sẽ không ảnh hưởng đến nhiều tối ưu hóa hữu ích khác, nhưng sẽ làm cho ngôn ngữ phù hợp hơn nhiều để xử lý dữ liệu nhận được từ các nguồn không đáng tin cậy.
supercat

@supercat: Thật vậy, hành vi không xác định không giống với hành vi không xác định. Chúng tôi đang thảo luận sau này
Lightness Races ở Orbit

7

Trình biên dịch có thể giả định rằng hành vi không xác định sẽ không xảy ra và bởi vì tràn đã ký là UB, nó có thể giả định rằng không bao giờ i * 12345678 > INT_MAX, do đó cũng i <= INT_MAX / 12345678 < 300và do đó loại bỏ kiểm tra i < 300.

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.