Câu trả lời hàng đầu là một quan niệm sai lầm (nhưng phổ biến):
Hành vi không xác định là thuộc tính thời gian chạy *. Nó KHÔNG THỂ "thời gian du lịch"!
Một số hoạt động nhất định được xác định (theo tiêu chuẩn) để có tác dụng phụ và không thể tối ưu hóa được. Các thao tác thực hiện I / O hoặc volatile
các biến truy cập thuộc loại này.
Tuy nhiên , có một lưu ý: UB có thể là bất kỳ hành vi nào , bao gồm cả hành vi hoàn tác các hoạt động trước đó. Điều này có thể gây ra hậu quả tương tự, trong một số trường hợp, đối với việc tối ưu hóa mã trước đó.
Trên thực tế, điều này phù hợp với câu trích dẫn trong câu trả lời trên cùng (phần nhấn mạnh của tôi):
Một triển khai tuân thủ thực hiện một chương trình được định dạng tốt sẽ tạo ra cùng một hành vi có thể quan sát được như một trong những thực thi có thể có của phiên bản tương ứng của máy trừu tượng có cùng chương trình và cùng một đầu vào.
Tuy nhiên, nếu bất kỳ quá trình thực hiện nào như vậy chứa một thao tác không xác định, thì tiêu chuẩn này không yêu cầu việc triển khai thực hiện chương trình đó với đầu vào đó (thậm chí không liên quan đến các thao tác trước thao tác không xác định đầu tiên).
Có, trích dẫn này nói "không liên quan đến các hoạt động trước hoạt động không xác định đầu tiên" , nhưng lưu ý rằng đây là đặc biệt về mã đang được thực thi , không chỉ được biên dịch.
Rốt cuộc, hành vi không xác định không thực sự được tiếp cận sẽ không làm gì cả và để dòng chứa UB thực sự được tiếp cận, mã đứng trước nó phải thực thi trước!
Vì vậy, có, một khi UB được thực thi , bất kỳ tác động nào của các hoạt động trước đó sẽ trở thành không xác định. Nhưng cho đến khi điều đó xảy ra, việc thực thi chương trình đã được xác định rõ.
Tuy nhiên, lưu ý rằng tất cả các lần thực thi chương trình dẫn đến việc này xảy ra có thể được tối ưu hóa cho các chương trình tương đương , bao gồm bất kỳ chương trình nào thực hiện các hoạt động trước đó nhưng sau đó bỏ tác dụng của chúng. Do đó, mã trước đó có thể được tối ưu hóa bất cứ khi nào làm như vậy sẽ tương đương với việc các hiệu ứng của chúng được hoàn tác ; nếu không, nó không thể. Xem ví dụ bên dưới.
* Lưu ý: Điều này không mâu thuẫn với UB xảy ra tại thời điểm biên dịch . Nếu trình biên dịch thực sự có thể chứng minh rằng mã UB sẽ luôn được thực thi cho tất cả các đầu vào, thì UB có thể kéo dài thời gian biên dịch. Tuy nhiên, điều này đòi hỏi phải biết rằng tất cả các mã trước đó cuối cùng sẽ trả về , đây là một yêu cầu mạnh mẽ. Một lần nữa, hãy xem ví dụ / giải thích bên dưới.
Để làm cho điều này cụ thể, hãy lưu ý rằng mã sau phải in foo
và chờ đầu vào của bạn bất kể hành vi không xác định nào theo sau nó:
printf("foo")
getchar()
*(char*)1 = 1
Tuy nhiên, cũng lưu ý rằng không có gì đảm bảo foo
sẽ vẫn còn trên màn hình sau khi UB xảy ra, hoặc ký tự bạn đã nhập sẽ không còn trong bộ đệm nhập nữa; cả hai thao tác này đều có thể được "hoàn tác", có tác dụng tương tự như "du hành thời gian" UB.
Nếu getchar()
dòng không có ở đó, sẽ hợp pháp nếu các dòng được tối ưu hóa nếu và chỉ khi điều đó không thể phân biệt được với việc xuất ra foo
và sau đó "hủy bỏ" nó.
Việc có thể phân biệt được hay không sẽ phụ thuộc hoàn toàn vào việc triển khai (tức là vào trình biên dịch và thư viện chuẩn của bạn). Ví dụ, bạn có thể printf
chặn luồng của bạn ở đây trong khi chờ chương trình khác đọc đầu ra không? Hay nó sẽ trở lại ngay lập tức?
Nếu nó có thể chặn ở đây, thì một chương trình khác có thể từ chối đọc đầu ra đầy đủ của nó, và nó có thể không bao giờ quay trở lại, và do đó UB có thể không bao giờ thực sự xảy ra.
Nếu nó có thể trở lại ngay lập tức ở đây, thì chúng ta biết nó phải trở lại, và do đó tối ưu hóa nó hoàn toàn không thể phân biệt được với việc thực thi nó và sau đó bỏ tác dụng của nó.
Tất nhiên, vì trình biên dịch biết hành vi nào được phép đối với phiên bản cụ thể của printf
nó, nó có thể tối ưu hóa tương ứng và do đó printf
có thể được tối ưu hóa trong một số trường hợp chứ không phải những trường hợp khác. Nhưng, một lần nữa, lời biện minh là điều này sẽ không thể phân biệt được với UB chưa thực hiện các hoạt động trước đó, chứ không phải mã trước đó bị "đầu độc" vì UB.
a
không được sử dụng (ngoại trừ để tính chính nó) và chỉ cần loại bỏa