Hãy xem cách tiêu chuẩn C định nghĩa các thuật ngữ "hành vi" và "hành vi không xác định".
Tài liệu tham khảo là dự thảo N1570 của tiêu chuẩn ISO C 2011; Tôi không biết về bất kỳ sự khác biệt liên quan nào trong bất kỳ tiêu chuẩn nào trong ba tiêu chuẩn ISO C đã được công bố (1990, 1999 và 2011).
Phần 3.4:
hành vi
xuất hiện bên ngoài hoặc hành động
Ok, điều đó hơi mơ hồ, nhưng tôi tranh luận rằng một câu lệnh đã cho không có "diện mạo" và chắc chắn không có "hành động", trừ khi nó thực sự được thực thi.
Mục 3.4.3:
hành vi ứng xử không xác định
, khi sử dụng cấu trúc chương trình không thể chuyển động hoặc có sai sót hoặc dữ liệu sai, mà tiêu chuẩn này không áp đặt yêu cầu
Nó nói " khi sử dụng " một cấu trúc như vậy. Từ "sử dụng" không được định nghĩa bởi tiêu chuẩn, vì vậy chúng tôi rơi trở lại nghĩa tiếng Anh thông thường. Một cấu trúc không được "sử dụng" nếu nó không bao giờ được thực thi.
Có một lưu ý dưới định nghĩa đó:
CHÚ THÍCH: Hành vi không xác định có thể xảy ra bao gồm từ bỏ qua hoàn toàn tình huống với kết quả không thể đoán trước, đến hành vi trong quá trình dịch hoặc thực thi chương trình theo cách thức được tài liệu hóa đặc trưng của môi trường (có hoặc không đưa ra thông báo chẩn đoán), đến việc chấm dứt bản dịch hoặc thực thi (với phát hành một thông báo chẩn đoán).
Vì vậy, một trình biên dịch được phép từ chối chương trình của bạn tại thời điểm biên dịch nếu hành vi của nó là không xác định. Nhưng giải thích của tôi về điều đó là nó có thể làm như vậy chỉ khi nó có thể chứng minh rằng mọi thực thi của chương trình sẽ gặp phải hành vi không xác định. Tôi nghĩ điều này ngụ ý rằng:
if (rand() % 2 == 0) {
i = i / 0;
}
mà chắc chắn có thể có hành vi không xác định, không thể bị từ chối tại thời điểm biên dịch.
Như một vấn đề thực tế, các chương trình phải có khả năng thực hiện các bài kiểm tra thời gian chạy để bảo vệ khỏi việc gọi ra hành vi không xác định và tiêu chuẩn phải cho phép chúng làm như vậy.
Ví dụ của bạn là:
if (0) {
i = 1/0;
}
mà không bao giờ thực hiện phép chia cho 0. Một thành ngữ rất phổ biến là:
int x, y;
if (y != 0) {
x = x / y;
}
Sự phân chia chắc chắn có hành vi không xác định nếu y == 0
, nhưng nó không bao giờ được thực thi nếu y == 0
. Hành vi được xác định rõ và vì lý do tương tự mà ví dụ của bạn cũng được xác định rõ: bởi vì hành vi không xác định tiềm ẩn có thể không bao giờ thực sự xảy ra.
(Trừ khi INT_MIN < -INT_MAX && x == INT_MIN && y == -1
(có, phép chia số nguyên có thể bị tràn), nhưng đó là một vấn đề riêng biệt.)
Trong một nhận xét (kể từ khi bị xóa), ai đó đã chỉ ra rằng trình biên dịch có thể đánh giá các biểu thức không đổi tại thời điểm biên dịch. Điều đó đúng, nhưng không liên quan trong trường hợp này, vì trong bối cảnh
i = 1/0;
1/0
không phải là một biểu thức hằng .
Một liên tục thể hiện là một phạm trù cú pháp làm giảm tới có điều kiện thể hiện (trong đó không bao gồm bài tập và biểu thức dấu phẩy). Việc sản xuất liên tục thể hiện xuất hiện trong ngữ pháp chỉ trong những bối cảnh thật sự yêu cầu một biểu thức hằng số, chẳng hạn như trường hợp nhãn. Vì vậy, nếu bạn viết:
switch (...) {
case 1/0:
...
}
sau đó 1/0
là một biểu thức hằng số - và một biểu thức vi phạm ràng buộc trong 6.6p4: "Mỗi biểu thức hằng số sẽ đánh giá một hằng số nằm trong phạm vi các giá trị có thể biểu diễn cho kiểu của nó.", vì vậy cần phải chẩn đoán. Nhưng phía bên phải của một phép gán không yêu cầu một biểu thức hằng , chỉ đơn thuần là một biểu thức điều kiện , vì vậy các ràng buộc đối với biểu thức hằng không áp dụng. Một trình biên dịch có thể đánh giá bất kỳ biểu thức nào mà nó có thể biên dịch tại thời điểm biên dịch, nhưng chỉ khi hành vi giống như khi nó được đánh giá trong khi thực thi (hoặc, trong ngữ cảnh if (0)
, không được đánh giá trong quá trình thực thi ().
(Một cái gì đó trông giống hệt như một biểu thức hằng không nhất thiết phải là một biểu thức hằng , cũng giống như, trong x + y * z
, chuỗi x + y
không phải là một biểu thức cộng do ngữ cảnh mà nó xuất hiện.)
Có nghĩa là chú thích trong N1570 phần 6.6 mà tôi sẽ trích dẫn:
Do đó, trong lần khởi tạo sau,
static int i = 2 || 1 / 0;
biểu thức là một biểu thức hằng số nguyên hợp lệ với giá trị là một.
không thực sự liên quan đến câu hỏi này.
Cuối cùng, có một số thứ được xác định là gây ra hành vi không xác định không liên quan đến những gì xảy ra trong quá trình thực thi. Phụ lục J, phần 2 của tiêu chuẩn C (xem lại bản dự thảo N1570 ) liệt kê những điều gây ra hành vi không xác định, được thu thập từ phần còn lại của tiêu chuẩn. Một số ví dụ (tôi không khẳng định đây là danh sách đầy đủ) là:
- Tệp nguồn trống không kết thúc bằng ký tự dòng mới không được đặt ngay trước ký tự gạch chéo ngược hoặc kết thúc bằng mã thông báo hoặc nhận xét tiền xử lý một phần
- Việc ghép mã thông báo tạo ra một chuỗi ký tự khớp với cú pháp của tên ký tự chung
- Một ký tự không có trong bộ ký tự nguồn cơ bản được gặp trong tệp nguồn, ngoại trừ trong một mã định danh, một hằng số ký tự, một chuỗi ký tự, tên tiêu đề, nhận xét hoặc mã thông báo tiền xử lý không bao giờ được chuyển đổi thành mã thông báo
- Một mã định danh, chú thích, ký tự chuỗi, hằng số ký tự hoặc tên tiêu đề chứa ký tự nhiều byte không hợp lệ hoặc không bắt đầu và kết thúc ở trạng thái thay đổi ban đầu
- Cùng một mã định danh có cả liên kết bên trong và bên ngoài trong cùng một đơn vị dịch
Những trường hợp cụ thể này là những thứ mà một trình biên dịch có thể phát hiện ra. Tôi nghĩ rằng hành vi của họ là không xác định vì ủy ban không muốn hoặc không thể áp đặt cùng một hành vi trên tất cả các triển khai và việc xác định một loạt các hành vi được phép chỉ là không đáng để nỗ lực. Chúng không thực sự thuộc loại "mã sẽ không bao giờ được thực thi", nhưng tôi đề cập đến chúng ở đây cho đầy đủ.