Hầu hết các loại UB mà chúng ta thường lo lắng, như NULL-deref hoặc chia cho 0, là UB thời gian chạy . Việc biên dịch một hàm sẽ gây ra UB thời gian chạy nếu được thực thi không được khiến trình biên dịch gặp sự cố. Trừ khi có thể nó có thể chứng minh rằng hàm (và đường dẫn qua hàm) chắc chắn sẽ được chương trình thực thi.
(Suy nghĩ thứ hai: có lẽ tôi chưa xem xét đánh giá mẫu / constexpr bắt buộc tại thời điểm biên dịch. Có thể UB trong thời gian đó được phép gây ra sự kỳ lạ tùy ý trong quá trình dịch ngay cả khi hàm kết quả không bao giờ được gọi.)
Hoạt động trong quá trình dịch phần trích dẫn ISO C ++ trong câu trả lời của @ StoryTeller tương tự như ngôn ngữ được sử dụng trong tiêu chuẩn ISO C. C không bao gồm các mẫu hoặc đánh giá constexpr
bắt buộc tại thời điểm biên dịch.
Nhưng thực tế thú vị : ISO C nói trong một lưu ý rằng nếu bản dịch bị chấm dứt, nó phải có thông báo chẩn đoán. Hoặc "cư xử trong khi dịch ... theo cách được ghi chép lại". Tôi không nghĩ rằng "bỏ qua hoàn toàn tình huống" có thể được đọc như bao gồm cả việc ngừng dịch.
Câu trả lời cũ, được viết trước khi tôi tìm hiểu về thời gian dịch thuật UB. Tuy nhiên, nó đúng với runtime-UB và do đó có khả năng vẫn hữu ích.
Không có điều gì như UB xảy ra tại thời điểm biên dịch. Nó có thể được hiển thị cho trình biên dịch dọc theo một đường dẫn thực thi nhất định, nhưng trong điều kiện C ++, nó đã không xảy ra cho đến khi việc thực thi đạt đến đường dẫn thực thi đó thông qua một hàm.
Các khiếm khuyết trong một chương trình khiến nó không thể biên dịch được thậm chí không phải là UB, chúng là lỗi cú pháp. Một chương trình như vậy là "không được hình thành tốt" trong thuật ngữ C ++ (nếu tôi có đúng tiêu chuẩn của tôi). Một chương trình có thể được hình thành tốt nhưng chứa UB. Sự khác biệt giữa Hành vi không xác định và Hình thành bệnh, không cần thông báo chẩn đoán
Trừ khi tôi hiểu sai điều gì đó, ISO C ++ yêu cầu chương trình này phải biên dịch và thực thi chính xác, bởi vì việc thực thi không bao giờ đạt đến chia hết cho không. (Trong thực tế ( Godbolt ), các trình biên dịch tốt chỉ làm cho các tệp thực thi hoạt động. Gcc / clang cảnh báo x / 0
nhưng không phải điều này, ngay cả khi tối ưu hóa. Nhưng dù sao, chúng tôi đang cố gắng cho biết ISO C ++ thấp cho phép chất lượng triển khai như thế nào. Vì vậy, hãy kiểm tra gcc / clang hầu như không phải là một bài kiểm tra hữu ích ngoài việc xác nhận rằng tôi đã viết chương trình một cách chính xác.)
int cause_UB() {
int x=0;
return 1 / x;
}
int main(){
if (0)
cause_UB();
}
Trường hợp sử dụng cho điều này có thể liên quan đến bộ tiền xử lý C, hoặc constexpr
các biến và phân nhánh trên các biến đó, điều này dẫn đến vô nghĩa trong một số đường dẫn không bao giờ đạt được cho các lựa chọn hằng số đó.
Các đường dẫn thực thi gây ra UB hiển thị thời gian biên dịch có thể được giả định là không bao giờ sử dụng, ví dụ như trình biên dịch cho x86 có thể phát ra một ud2
(gây ra ngoại lệ lệnh bất hợp pháp) như định nghĩa cho cause_UB()
. Hoặc trong một hàm, nếu một bên của một bên if()
dẫn đến UB có thể chứng minh được , nhánh có thể bị loại bỏ.
Nhưng trình biên dịch vẫn phải biên dịch mọi thứ khác một cách lành mạnh và chính xác. Tất cả các đường dẫn không gặp phải (hoặc không thể được chứng minh là gặp) UB vẫn phải được biên dịch để asm thực thi như thể máy trừu tượng C ++ đang chạy nó.
Bạn có thể tranh luận rằng UB hiển thị theo thời gian biên dịch vô điều kiện main
là một ngoại lệ đối với quy tắc này. Hoặc nếu không thì có thể biên dịch theo thời gian mà việc thực thi bắt đầu từ main
thực tế đạt được UB được đảm bảo.
Tôi vẫn tranh luận rằng các hành vi của trình biên dịch hợp pháp bao gồm việc tạo ra một quả lựu đạn phát nổ nếu chạy. Hay hợp lý hơn, một định nghĩa về main
điều đó bao gồm một chỉ dẫn bất hợp pháp. Tôi lập luận rằng nếu bạn không bao giờ chạy chương trình, thì vẫn chưa có bất kỳ UB nào. Bản thân trình biên dịch không được phép phát nổ, IMO.
Các hàm chứa UB khả thi hoặc có thể cho phép bên trong các nhánh
UB dọc theo bất kỳ đường dẫn thực thi nhất định nào sẽ quay ngược thời gian để "làm ô nhiễm" tất cả các mã trước đó. Nhưng trong thực tế, các trình biên dịch chỉ có thể tận dụng quy tắc đó khi họ thực sự có thể chứng minh rằng các đường dẫn thực thi dẫn đến UB biên dịch-thời gian hiển thị. ví dụ
int minefield(int x) {
if (x == 3) {
*(char*)nullptr = x/0;
}
return x * 5;
}
Trình biên dịch phải làm cho asm hoạt động cho tất cả x
những thứ khác ngoài 3, cho đến điểm x * 5
gây ra tràn UB có dấu tại INT_MIN và INT_MAX. Nếu hàm này không bao giờ được gọi với x==3
, chương trình tất nhiên không chứa UB và phải hoạt động như đã viết.
Chúng tôi cũng có thể đã viết bằng if(x == 3) __builtin_unreachable();
GNU C để nói với trình biên dịch rằng x
chắc chắn không phải là 3.
Trên thực tế, có mã "bãi mìn" ở khắp nơi trong các chương trình bình thường. ví dụ: bất kỳ phép chia nào cho một số nguyên hứa hẹn với trình biên dịch rằng nó khác 0. Bất kỳ con trỏ nào deref hứa với trình biên dịch rằng nó không phải là NULL.