Có hợp pháp không khi mã nguồn chứa hành vi không xác định làm sập trình biên dịch?


85

Giả sử tôi đi biên dịch một số mã nguồn C ++ được viết kém mà gọi hành vi không xác định, và do đó (như họ nói) "bất cứ điều gì có thể xảy ra".

Từ góc độ của đặc điểm kỹ thuật ngôn ngữ C ++ được coi là có thể chấp nhận được trong trình biên dịch "tuân thủ", "bất kỳ điều gì" trong trường hợp này có bao gồm trình biên dịch bị lỗi (hoặc lấy cắp mật khẩu của tôi, hoặc hoạt động sai hoặc lỗi tại thời điểm biên dịch) hay là phạm vi của hành vi không xác định được giới hạn cụ thể cho những gì có thể xảy ra khi chạy thực thi kết quả?


22
"UB là UB. Sống với nó" ... Không cần chờ đợi. "Vui lòng đăng MCVE." ... Không chờ đợi. Tôi thích câu hỏi vì tất cả những phản xạ mà nó gây ra một cách không phù hợp. :-)
Yunnosch

14
Thực sự không có giới hạn, đó là lý do người ta nói rằng UB có thể triệu hồi quỷ mũi .
Một số lập trình viên dude

15
UB có thể bắt tác giả đăng câu hỏi trên SO. : P
Tanveer Badar

45
Bất kể tiêu chuẩn C ++ nói gì, nếu tôi là một người viết trình biên dịch, tôi chắc chắn sẽ coi nó như một lỗi trong trình biên dịch của tôi. Vì vậy, nếu bạn thấy điều này, hãy gửi báo cáo lỗi.
john

9
@LeifWillerts Điều này đã trở lại vào những năm 80. Tôi không nhớ cấu trúc chính xác, nhưng nghĩ rằng nó phụ thuộc vào việc sử dụng một loại biến phức tạp. Sau khi tôi thay thế tôi đã có một khoảnh khắc "tôi đang nghĩ gì - mọi thứ không hoạt động theo cách đó". Tôi không đổ lỗi cho trình biên dịch vì đã từ chối cấu trúc, chỉ vì khởi động lại máy. Tôi nghi ngờ bất cứ ai sẽ gặp phải trình biên dịch đó ngày hôm nay. Đó là trình biên dịch chéo HP C cho HP 64000 nhắm mục tiêu bộ vi xử lý 68000.
Avi Berger

Câu trả lời:


71

Định nghĩa chuẩn mực của hành vi không xác định như sau:

[defns.undefined]

hành vi mà tiêu chuẩn này không áp đặt yêu cầu

[Lưu ý: Hành vi không xác định có thể xảy ra khi Tiêu chuẩn này bỏ qua bất kỳ định nghĩa rõ ràng nào về hành vi hoặc khi một chương trình sử dụng cấu trúc sai hoặc dữ liệu sai. Hành vi không xác định được phép có phạm vi từ việc 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 lập thành văn bản đặ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 việc phát hành của một thông báo chẩn đoán). Nhiều cấu trúc chương trình sai lầm không tạo ra hành vi không xác định; họ bắt buộc phải được chẩn đoán. Việc đánh giá một biểu thức hằng không bao giờ thể hiện hành vi được chỉ định rõ ràng là không xác định. - ghi chú cuối]

Mặc dù bản thân ghi chú không phải là quy chuẩn, nhưng nó mô tả một loạt các cách triển khai hành vi được biết đến. Vì vậy, việc sập trình biên dịch (đang dịch đột ngột kết thúc), theo lưu ý đó là hợp pháp. Nhưng thực sự, như văn bản quy chuẩn đã nói, tiêu chuẩn không đặt ra bất kỳ giới hạn nào cho việc thực thi hoặc dịch thuật. Nếu một triển khai đánh cắp mật khẩu của bạn, điều đó không vi phạm bất kỳ hợp đồng nào được quy định trong tiêu chuẩn.


43
Điều đó nói rằng, nếu bạn thực sự có thể có được một trình biên dịch để thực thi mã tùy ý tại thời điểm biên dịch mà không cần bất kỳ hộp cát nào, thì những người bảo mật khác nhau sẽ rất muốn biết về nó. Điều này cũng xảy ra với trình biên dịch mặc định.
Kevin,

67
Ditto cho những gì Kevin đã nói. Là một kỹ sư biên dịch C / C ++ / etc trong sự nghiệp trước đây, quan điểm của chúng tôi là hành vi không xác định có thể làm hỏng chương trình của bạn , làm hỏng dữ liệu đầu ra của bạn, đốt cháy ngôi nhà của bạn, bất cứ điều gì. Nhưng trình biên dịch sẽ không bao giờ bị lỗi cho dù đầu vào là gì. (Nó có thể không cung cấp cho các thông báo lỗi hữu ích, nhưng nó phải sản xuất một số loại chẩn đoán và thoát chứ không phải chỉ la hét Cthulhu Take the Wheel và segfaulting.)
Ti Strga

8
@TiStrga Tôi cá rằng Cthulhu sẽ tạo ra một tay đua F1 tuyệt vời.
zeta-band

35
"Nếu một triển khai đánh cắp mật khẩu của bạn, điều đó không vi phạm bất kỳ hợp đồng nào được quy định trong tiêu chuẩn." Điều đó đúng bất kể mã có UB, phải không? Tiêu chuẩn chỉ ra lệnh chương trình đã biên dịch phải làm gì - một trình biên dịch biên dịch đúng mã nhưng lấy cắp mật khẩu của bạn trong quá trình này sẽ không tuân theo tiêu chuẩn.
Carmeister

8
@Carmeister, oooh, đó là một điểm tốt, tôi đảm bảo sẽ nhắc mọi người về điều đó bất cứ khi nào những lập luận "UB cho phép trình biên dịch bắt đầu chiến tranh hạt nhân" bật lên. Lần nữa.
ilkkachu

8

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á constexprbắ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 / 0như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;      // UB if ever reached.
 // Note I'm avoiding  x/0  in case that counts as translation time UB.
 // UB still obvious when optimizing across statements, though.
}

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 constexprcá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 mainlà 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ừ mainthự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ả xnhững thứ khác ngoài 3, cho đến điểm x * 5gâ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 xchắ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.


3

"Hợp pháp" ở đây có nghĩa là gì? Theo các tiêu chuẩn này, bất kỳ điều gì không mâu thuẫn với tiêu chuẩn C hoặc tiêu chuẩn C ++ đều hợp pháp. Nếu bạn thực hiện một tuyên bố i = i++;và kết quả là khủng long chiếm lấy thế giới, điều đó không mâu thuẫn với các tiêu chuẩn. Tuy nhiên, nó mâu thuẫn với các định luật vật lý, vì vậy nó sẽ không xảy ra :-)

Nếu hành vi không xác định làm hỏng trình biên dịch của bạn, điều đó không vi phạm tiêu chuẩn C hoặc C ++. Tuy nhiên, điều đó có nghĩa là chất lượng của trình biên dịch có thể (và có lẽ nên) được cải thiện.

Trong các phiên bản trước của tiêu chuẩn C, có những câu lệnh bị lỗi hoặc không phụ thuộc vào hành vi không xác định:

char* p = 1 / 0;

Việc gán hằng số 0 cho một ký tự * được phép. Không cho phép một hằng số khác 0. Vì giá trị của 1/0 là hành vi không xác định, nên việc trình biên dịch có nên hay không chấp nhận câu lệnh này là hành vi không xác định. (Ngày nay, 1/0 không còn đáp ứng định nghĩa của "biểu thức hằng số nguyên" nữa).


3
Nói một cách chính xác: khủng long chiếm lĩnh thế giới không mâu thuẫn với bất kỳ định luật vật lý nào (ví dụ như biến thể Công viên kỷ Jura). Nó chỉ là rất khó xảy ra. :)
kỳ lạ
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.