Đây là UB; trong thuật ngữ ISO C ++, toàn bộ hành vi của toàn bộ chương trình hoàn toàn không được chỉ định cho một thực thi cuối cùng đạt được UB. Ví dụ kinh điển là theo tiêu chuẩn C ++, nó có thể khiến quỷ bay ra khỏi mũi bạn. (Tôi khuyên bạn không nên sử dụng một triển khai trong đó quỷ mũi là một khả năng thực sự). Xem câu trả lời khác để biết thêm chi tiết.
Trình biên dịch có thể "gây rắc rối" tại thời gian biên dịch cho các đường dẫn thực thi mà chúng có thể thấy dẫn đến UB biên dịch theo thời gian biên dịch, ví dụ giả sử các khối cơ bản đó không bao giờ đạt được.
Xem thêm Những gì mỗi lập trình viên C nên biết về hành vi không xác định (blog LLVM). Như đã giải thích ở đó, UB tràn đã ký cho phép trình biên dịch chứng minh rằng for(... i <= n ...)
các vòng lặp không phải là các vòng lặp vô hạn, ngay cả khi chưa biết n
. Nó cũng cho phép họ "quảng bá" bộ đếm vòng lặp int thành chiều rộng con trỏ thay vì làm lại phần mở rộng dấu hiệu. (Vì vậy, hậu quả của UB trong trường hợp đó có thể là truy cập bên ngoài các yếu tố 64k hoặc 4G thấp của một mảng, nếu bạn đang mong đợi việc đóng gói có chữ ký i
vào phạm vi giá trị của nó.)
Trong một số trường hợp, trình biên dịch sẽ phát ra một lệnh bất hợp pháp như x86 ud2
cho một khối có thể gây ra UB nếu được thực thi. (Lưu ý rằng một hàm có thể chưa bao giờ được gọi, do đó, trình biên dịch nói chung không thể đi berserk và phá vỡ các hàm khác, hoặc thậm chí các đường dẫn có thể thông qua một hàm không nhấn UB. Tức là mã máy mà nó biên dịch vẫn phải hoạt động tất cả các yếu tố đầu vào không dẫn đến UB.)
Có lẽ giải pháp hiệu quả nhất là tự bóc lớp lặp cuối cùng để không cần thiết factor*=10
có thể tránh được.
int result = 0;
int factor = 1;
for (... i < n-1) { // stop 1 iteration early
result = ...
factor *= 10;
}
result = ... // another copy of the loop body, using the last factor
// factor *= 10; // and optimize away this dead operation.
return result;
Hoặc nếu thân vòng lặp lớn, hãy xem xét đơn giản bằng cách sử dụng loại không dấu cho factor
. Sau đó, bạn có thể để tràn bội số không dấu và nó sẽ chỉ thực hiện gói được xác định rõ với một số lũy thừa là 2 (số bit giá trị trong loại không dấu).
Điều này tốt ngay cả khi bạn sử dụng nó với các loại đã ký, đặc biệt là nếu chuyển đổi chưa ký-> đã ký của bạn không bao giờ tràn.
Chuyển đổi giữa phần bổ sung không dấu và 2 được ký là miễn phí (cùng mẫu bit cho tất cả các giá trị); gói modulo cho int -> không dấu được chỉ định bởi tiêu chuẩn C ++ đơn giản hóa việc chỉ sử dụng cùng một mẫu bit, không giống như bổ sung hoặc ký hiệu / cường độ của một người.
Và unsign-> đã ký là tương tự tầm thường, mặc dù nó được xác định theo triển khai cho các giá trị lớn hơn INT_MAX
. Nếu bạn không sử dụng kết quả không dấu lớn từ lần lặp cuối cùng, bạn không có gì phải lo lắng. Nhưng nếu bạn là, hãy xem Có phải chuyển đổi từ không dấu sang ký không xác định? . Trường hợp giá trị không phù hợp được xác định theo thực thi , có nghĩa là việc triển khai phải chọn một số hành vi; những người lành mạnh chỉ cắt bớt (nếu cần) mẫu bit không dấu và sử dụng nó như đã ký, bởi vì nó hoạt động cho các giá trị trong phạm vi theo cùng một cách mà không cần làm thêm. Và nó chắc chắn không phải là UB. Vì vậy, các giá trị không dấu lớn có thể trở thành số nguyên ký âm. ví dụ: sau khi int x = u;
gcc và clang không tối ưu hóa đix>=0
như luôn luôn đúng, thậm chí không có -fwrapv
, bởi vì họ xác định hành vi.