Sự tồn tại của một câu lệnh như vậy trong một chương trình nhất định có nghĩa là toàn bộ chương trình là không xác định hoặc hành vi đó chỉ trở thành không xác định khi luồng điều khiển chạm vào câu lệnh này?
Cũng không. Điều kiện đầu tiên là quá mạnh và điều kiện thứ hai là quá yếu.
Việc truy cập đối tượng đôi khi được sắp xếp theo trình tự, nhưng tiêu chuẩn mô tả hoạt động của chương trình bên ngoài thời gian. Danvil đã được trích dẫn:
nếu bất kỳ quá trình thực thi 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 hoạt động trước thao tác không xác định đầu tiên)
Điều này có thể được hiểu:
Nếu việc thực thi chương trình mang lại hành vi không xác định, thì toàn bộ chương trình có hành vi không xác định.
Vì vậy, một tuyên bố không liên lạc được với UB không mang lại cho UB chương trình. Một câu lệnh có thể truy cập được (vì các giá trị của đầu vào) không bao giờ đạt được, không cung cấp cho chương trình UB. Đó là lý do tại sao điều kiện đầu tiên của bạn quá mạnh.
Bây giờ, trình biên dịch nói chung không thể nói những gì có UB. Vì vậy, để cho phép trình tối ưu hóa sắp xếp lại các câu lệnh với UB tiềm năng có thể sắp xếp lại thứ tự nếu hành vi của chúng được xác định, cần cho phép UB "quay ngược thời gian" và sai trước điểm trình tự trước đó (hoặc trong C ++ 11 thuật ngữ, để UB ảnh hưởng đến những thứ được sắp xếp trước thứ UB). Do đó tình trạng thứ hai của bạn quá yếu.
Một ví dụ chính của điều này là khi trình tối ưu hóa dựa vào răng cưa nghiêm ngặt. Toàn bộ điểm của các quy tắc bí danh nghiêm ngặt là cho phép trình biên dịch sắp xếp lại các hoạt động không thể được sắp xếp lại một cách hợp lệ nếu các con trỏ được đề cập có bí danh trên cùng một bộ nhớ. Vì vậy, nếu bạn sử dụng con trỏ răng cưa bất hợp pháp và UB xảy ra, thì nó có thể dễ dàng ảnh hưởng đến một câu lệnh "trước" câu lệnh UB. Đối với máy trừu tượng có liên quan, câu lệnh UB vẫn chưa được thực thi. Theo như mã đối tượng thực tế có liên quan, nó đã được thực thi một phần hoặc toàn bộ. Nhưng tiêu chuẩn không cố gắng đi vào chi tiết về ý nghĩa của việc trình tối ưu hóa sắp xếp lại các câu lệnh hoặc ý nghĩa của điều đó đối với UB. Nó chỉ đưa ra giấy phép triển khai sai ngay khi nó vừa ý.
Bạn có thể nghĩ về điều này là, "UB có một cỗ máy thời gian".
Cụ thể để trả lời các ví dụ của bạn:
- Hành vi chỉ là không xác định nếu 3 được đọc.
- Các trình biên dịch có thể và thực hiện loại bỏ mã như đã chết nếu một khối cơ bản chứa một hoạt động nhất định chưa được xác định. Chúng được phép (và tôi đoán là có) trong những trường hợp không phải là một khối cơ bản nhưng ở đó tất cả các nhánh đều dẫn đến UB. Ví dụ này không phải là một ứng cử viên trừ khi
PrintToConsole(3)
bằng cách nào đó được biết là chắc chắn sẽ quay trở lại. Nó có thể ném ra một ngoại lệ hoặc bất cứ điều gì.
Một ví dụ tương tự như tùy chọn thứ hai của bạn là tùy chọn gcc -fdelete-null-pointer-checks
, có thể lấy mã như thế này (Tôi chưa kiểm tra ví dụ cụ thể này, hãy coi nó là minh họa cho ý tưởng chung):
void foo(int *p) {
if (p) *p = 3;
std::cout << *p << '\n';
}
và thay đổi nó thành:
*p = 3;
std::cout << "3\n";
Tại sao? Bởi vì nếu p
là null thì mã vẫn có UB, vì vậy trình biên dịch có thể cho rằng nó không phải là null và tối ưu hóa cho phù hợp. Kernel linux vấp này ( https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2009-1897 ) chủ yếu bởi vì nó hoạt động trong một chế độ mà dereferencing một con trỏ null không phải là UB, dự kiến sẽ dẫn đến một ngoại lệ phần cứng được xác định mà hạt nhân có thể xử lý. Khi tối ưu hóa được bật, gcc yêu cầu sử dụng -fno-delete-null-pointer-checks
để đảm bảo vượt quá tiêu chuẩn đó.
Tái bút Câu trả lời thiết thực cho câu hỏi "khi nào thì hành vi không xác định xảy ra?" là "10 phút trước khi bạn dự định đi trong ngày".