Hành vi không xác định trong C và C ++ là gì? Điều gì về hành vi không xác định và hành vi xác định thực hiện? sự khác biệt giữa chúng là gì?
Hành vi không xác định trong C và C ++ là gì? Điều gì về hành vi không xác định và hành vi xác định thực hiện? sự khác biệt giữa chúng là gì?
Câu trả lời:
Hành vi không xác định là một trong những khía cạnh của ngôn ngữ C và C ++ có thể gây ngạc nhiên cho các lập trình viên đến từ các ngôn ngữ khác (các ngôn ngữ khác cố gắng che giấu nó tốt hơn). Về cơ bản, có thể viết các chương trình C ++ không hoạt động theo cách có thể dự đoán được, mặc dù nhiều trình biên dịch C ++ sẽ không báo cáo bất kỳ lỗi nào trong chương trình!
Hãy xem xét một ví dụ cổ điển:
#include <iostream>
int main()
{
char* p = "hello!\n"; // yes I know, deprecated conversion
p[0] = 'y';
p[5] = 'w';
std::cout << p;
}
Biến p
chỉ vào chuỗi ký tự "hello!\n"
và hai phép gán bên dưới cố gắng sửa đổi chuỗi ký tự đó. Chương trình này làm gì? Theo mục 2.14.5 đoạn 11 của tiêu chuẩn C ++, nó gọi hành vi không xác định :
Hiệu quả của việc cố gắng sửa đổi một chuỗi ký tự là không xác định.
Tôi có thể nghe thấy mọi người hét lên "Nhưng chờ đã, tôi có thể biên dịch điều này không có vấn đề gì và nhận được đầu ra yellow
" hoặc "Ý bạn là gì không xác định, chuỗi ký tự được lưu trữ trong bộ nhớ chỉ đọc, vì vậy lần thử đầu tiên sẽ dẫn đến kết xuất lõi". Đây chính xác là vấn đề với hành vi không xác định. Về cơ bản, tiêu chuẩn cho phép bất cứ điều gì xảy ra một khi bạn gọi hành vi không xác định (thậm chí là quỷ mũi). Nếu có một hành vi "đúng" theo mô hình ngôn ngữ tinh thần của bạn, thì mô hình đó đơn giản là sai; Các tiêu chuẩn C ++ có phiếu bầu duy nhất, thời gian.
Các ví dụ khác về hành vi không xác định bao gồm truy cập vào một mảng nằm ngoài giới hạn của nó, hủy bỏ con trỏ null , truy cập các đối tượng sau khi thời gian kết thúc của chúng hoặc viết các biểu thức được cho là thông minh như thế nào i++ + ++i
.
Phần 1.9 của tiêu chuẩn C ++ cũng đề cập đến hai anh em ít nguy hiểm hơn, hành vi không xác định và hành vi được xác định thực hiện :
Các mô tả ngữ nghĩa trong Tiêu chuẩn quốc tế này xác định một máy trừu tượng không tham số hóa.
Một số khía cạnh và hoạt động của máy trừu tượng được mô tả trong Tiêu chuẩn quốc tế này dưới dạng xác định thực hiện (ví dụ
sizeof(int)
:). Chúng tạo thành các tham số của máy trừu tượng. Mỗi thực hiện sẽ bao gồm các tài liệu mô tả các đặc điểm và hành vi của nó trong các khía cạnh này.Một số khía cạnh và hoạt động khác của máy trừu tượng được mô tả trong Tiêu chuẩn quốc tế này là không xác định (ví dụ: thứ tự đánh giá các đối số cho một hàm). Khi có thể, Tiêu chuẩn quốc tế này xác định một tập hợp các hành vi được phép. Chúng xác định các khía cạnh không xác định của máy trừu tượng.
Một số hoạt động khác được mô tả trong Tiêu chuẩn quốc tế này là không xác định (ví dụ: hiệu ứng của việc hủy bỏ con trỏ null). [ Lưu ý : Tiêu chuẩn quốc tế này áp đặt không có yêu cầu đối với hành vi của các chương trình có chứa hành vi không xác định. - lưu ý cuối ]
Cụ thể, mục 1.3.24 nêu:
Hành vi không xác định cho phép 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 thuật hoặc thực hiện chương trình theo đặc tính tài liệu của môi trường (có hoặc không có thông báo chẩn đoán), để chấm dứt dịch thuật hoặc thực thi (với việc ban hành của một thông điệp chẩn đoán).
Bạn có thể làm gì để tránh chạy vào hành vi không xác định? Về cơ bản, bạn phải đọc những cuốn sách C ++ hay của những tác giả biết họ đang nói về cái gì. Vít hướng dẫn internet. Vít bullschildt.
int f(){int a; return a;}
: giá trị của a
có thể thay đổi giữa các lệnh gọi hàm.
Vâng, về cơ bản, đây là một bản sao dán thẳng từ tiêu chuẩn
3.4.1 1 hành vi được xác định theo thực hiện Hành vi không xác định trong đó mỗi tài liệu thực hiện cách lựa chọn được thực hiện
2 VÍ DỤ Một ví dụ về hành vi được xác định thực hiện là sự lan truyền của bit thứ tự cao khi một số nguyên được ký được dịch chuyển sang phải.
3.4.3 1 hành vi hành vi không xác định , khi sử dụng cấu trúc chương trình không thể truy cập hoặc có lỗi hoặc dữ liệu sai, mà Tiêu chuẩn quốc tế này áp đặt không có yêu cầu
2 LƯU Ý Hành vi không xác định có thể 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 hiện chương trình theo đặc tính của tài liệu về môi trường (có hoặc không có thông báo chẩn đoán), để chấm dứt dịch hoặc thực hiện (với việc phát hành một thông điệp chẩn đoán).
3 VÍ DỤ Một ví dụ về hành vi không xác định là hành vi trên tràn số nguyên.
3.4.4 1 hành vi không xác định sử dụng một giá trị không xác định hoặc hành vi khác trong đó Tiêu chuẩn quốc tế này cung cấp hai hoặc nhiều khả năng và không áp dụng thêm các yêu cầu nào được chọn trong bất kỳ trường hợp nào
2 VÍ DỤ Một ví dụ về hành vi không xác định là thứ tự mà các đối số cho hàm được ước tính.
int foo(int x) { if (x >= 0) launch_missiles(); return x << 1; }
một trình biên dịch có thể xác định rằng vì tất cả các phương tiện gọi hàm không khởi động tên lửa đều gọi Hành vi không xác định, nên nó có thể thực hiện cuộc gọi đến launch_missiles()
vô điều kiện.
Có lẽ từ ngữ dễ hiểu có thể dễ hiểu hơn định nghĩa khắt khe của các tiêu chuẩn.
hành vi xác định thực hiện
Ngôn ngữ nói rằng chúng ta có kiểu dữ liệu. Các nhà cung cấp trình biên dịch xác định kích thước họ sẽ sử dụng và cung cấp tài liệu về những gì họ đã làm.
hành vi không xác định
Bạn đang làm gì đó sai. Ví dụ: bạn có một giá trị rất lớn int
không phù hợp char
. Làm thế nào để bạn đặt giá trị đó trong char
? Thật ra không có cách nào! Bất cứ điều gì cũng có thể xảy ra, nhưng điều hợp lý nhất sẽ là lấy byte đầu tiên của int đó và đặt nó vào char
. Thật sai lầm khi chỉ định byte đầu tiên, nhưng đó là những gì xảy ra dưới mui xe.
hành vi không xác định
Chức năng nào của hai điều này được thực hiện trước?
void fun(int n, int m);
int fun1()
{
cout << "fun1";
return 1;
}
int fun2()
{
cout << "fun2";
return 2;
}
...
fun(fun1(), fun2()); // which one is executed first?
Ngôn ngữ không chỉ định đánh giá, từ trái sang phải hoặc phải sang trái! Vì vậy, một hành vi không xác định có thể hoặc không thể dẫn đến một hành vi không xác định, nhưng chắc chắn chương trình của bạn không được tạo ra một hành vi không xác định.
@eSKay Tôi nghĩ rằng câu hỏi của bạn đáng để chỉnh sửa câu trả lời để làm rõ hơn :)
cho
fun(fun1(), fun2());
không phải là hành vi "thực hiện được xác định"? Trình biên dịch phải chọn một hoặc khóa học khác, sau tất cả?
Sự khác biệt giữa định nghĩa triển khai và không xác định, là trình biên dịch có nghĩa vụ chọn một hành vi trong trường hợp đầu tiên nhưng nó không phải trong trường hợp thứ hai. Ví dụ, một triển khai phải có một và chỉ một định nghĩa về sizeof(int)
. Vì vậy, không thể nói đó sizeof(int)
là 4 cho một số phần của chương trình và 8 cho các phần khác. Không giống như hành vi không xác định, nơi trình biên dịch có thể nói OK Tôi sẽ đánh giá các đối số này từ trái sang phải và các đối số của hàm tiếp theo được đánh giá từ phải sang trái. Nó có thể xảy ra trong cùng một chương trình, đó là lý do tại sao nó được gọi là không xác định . Trên thực tế, C ++ có thể đã được thực hiện dễ dàng hơn nếu một số hành vi không xác định được chỉ định. Hãy xem câu trả lời của Tiến sĩ Stroustrup cho điều đó :
Người ta khẳng định rằng sự khác biệt giữa những gì có thể được tạo ra mang lại cho trình biên dịch sự tự do này và yêu cầu "đánh giá từ trái sang phải" thông thường có thể là đáng kể. Tôi không tin, nhưng với vô số trình biên dịch "ngoài kia" lợi dụng tự do và một số người nhiệt tình bảo vệ tự do đó, một sự thay đổi sẽ khó khăn và có thể mất hàng thập kỷ để thâm nhập vào các góc xa của thế giới C và C ++. Tôi thất vọng vì không phải tất cả các trình biên dịch đều cảnh báo chống lại mã như ++ i + i ++. Tương tự, thứ tự đánh giá các đối số là không xác định.
IMO quá nhiều "điều" không được xác định, không xác định, xác định thực hiện, v.v. Tuy nhiên, điều đó dễ nói và thậm chí để đưa ra ví dụ về, nhưng khó sửa. Cũng cần lưu ý rằng không khó để tránh hầu hết các vấn đề và tạo mã di động.
fun(fun1(), fun2());
không phải là hành vi "implementation defined"
? Trình biên dịch phải chọn một hoặc khóa học khác, sau tất cả?
"I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left"
tôi hiểu điều này can
xảy ra. Có thực sự, với trình biên dịch mà chúng ta sử dụng ngày nay?
Từ tài liệu hợp lý C chính thức
Các thuật ngữ hành vi không xác định , hành vi không xác định và hành vi được xác định thực hiện được sử dụng để phân loại kết quả của việc viết chương trình có các thuộc tính mà Tiêu chuẩn không hoặc không thể mô tả hoàn toàn. Mục tiêu của việc áp dụng phân loại này là cho phép một số loại nhất định trong số các triển khai cho phép chất lượng thực hiện trở thành một lực lượng tích cực trên thị trường cũng như cho phép các tiện ích mở rộng phổ biến nhất định, mà không loại bỏ bộ đệm tuân thủ Tiêu chuẩn. Phụ lục F của Danh mục tiêu chuẩn những hành vi thuộc một trong ba loại này.
Hành vi không xác định cung cấp cho người thực hiện một số vĩ độ trong việc dịch các chương trình. Vĩ độ này không kéo dài đến mức không thể dịch chương trình.
Hành vi không xác định cung cấp cho giấy phép người thực hiện không bắt lỗi nhất định của chương trình khó chẩn đoán. Nó cũng xác định các khu vực có thể mở rộng ngôn ngữ phù hợp: người triển khai có thể tăng ngôn ngữ bằng cách cung cấp định nghĩa về hành vi không xác định chính thức.
Hành vi được xác định theo thực hiện cho phép người thực hiện tự do lựa chọn cách tiếp cận phù hợp, nhưng yêu cầu lựa chọn này phải được giải thích cho người dùng. Các hành vi được chỉ định là định nghĩa triển khai thường là những hành vi mà người dùng có thể đưa ra quyết định mã hóa có ý nghĩa dựa trên định nghĩa triển khai. Các nhà triển khai nên ghi nhớ tiêu chí này khi quyết định mức độ mở rộng của một định nghĩa triển khai. Như với hành vi không xác định, chỉ đơn giản là không dịch nguồn có chứa hành vi được xác định thực hiện không phải là một phản hồi thích hợp.
Hành vi không xác định so với Hành vi không xác định có một mô tả ngắn về nó.
Tóm tắt cuối cùng của họ:
Tóm lại, hành vi không xác định thường là điều bạn không nên lo lắng, trừ khi phần mềm của bạn bắt buộc phải có thể mang theo được. Ngược lại, hành vi không xác định luôn luôn là không mong muốn và không bao giờ nên xảy ra.
Về mặt lịch sử, cả hai Hành vi được xác định theo thực hiện và Hành vi không xác định đều thể hiện các tình huống trong đó các tác giả của Tiêu chuẩn dự kiến rằng mọi người viết các triển khai chất lượng sẽ sử dụng phán đoán để quyết định những gì đảm bảo hành vi, nếu có, sẽ hữu ích cho các chương trình trong trường ứng dụng dự định chạy trên mục tiêu dự định. Nhu cầu của mã crunching số cao cấp khá khác biệt so với mã hệ thống cấp thấp, và cả UB và IDB đều cho phép người viết trình biên dịch linh hoạt để đáp ứng các nhu cầu khác nhau đó. Không thể loại nào bắt buộc các triển khai thực hiện theo cách hữu ích cho bất kỳ mục đích cụ thể nào, hoặc thậm chí cho bất kỳ mục đích nào. Tuy nhiên, việc triển khai chất lượng được cho là phù hợp cho một mục đích cụ thể, tuy nhiên, nên hành xử theo cách phù hợp với mục đích đódù Tiêu chuẩn có yêu cầu hay không .
Sự khác biệt duy nhất giữa Hành vi được Xác định Thực hiện và Hành vi Không xác định là trước đây yêu cầu việc triển khai xác định và ghi lại hành vi nhất quán ngay cả trong trường hợp việc triển khai không thể làm gì có thể hữu ích . Đường phân chia giữa chúng không phải là liệu có hữu ích cho việc triển khai để xác định hành vi hay không (người viết trình biên dịch nên xác định hành vi hữu ích khi thực tế cho dù Tiêu chuẩn có yêu cầu chúng hay không) mà là có thể thực hiện đồng thời việc xác định hành vi có tốn kém không và vô dụng . Một phán quyết rằng việc triển khai như vậy có thể tồn tại không theo bất kỳ cách nào, hình dạng hoặc hình thức, ngụ ý bất kỳ phán xét nào về tính hữu ích của việc hỗ trợ một hành vi được xác định trên các nền tảng khác.
Thật không may, kể từ giữa những năm 1990, các nhà văn biên dịch đã bắt đầu hiểu việc thiếu các nhiệm vụ hành vi là một phán quyết rằng đảm bảo hành vi không đáng giá ngay cả trong các lĩnh vực ứng dụng, nơi chúng rất quan trọng và thậm chí trên các hệ thống mà thực tế chúng không tốn kém. Thay vì điều trị UB như một lời mời để thực hiện phán quyết hợp lý, nhà văn biên dịch đã bắt đầu xử lý nó như một cái cớ không làm như vậy.
Ví dụ, đưa ra mã sau đây:
int scaled_velocity(int v, unsigned char pow)
{
if (v > 250)
v = 250;
if (v < -250)
v = -250;
return v << pow;
}
việc thực hiện bổ sung hai sẽ không phải tốn bất kỳ nỗ lực nào để coi biểu thức v << pow
là sự thay đổi bổ sung của hai mà không quan tâm đến việc v
tích cực hay tiêu cực.
Tuy nhiên, triết lý ưa thích của một số người viết trình biên dịch ngày nay sẽ đề xuất rằng vì v
chỉ có thể âm nếu chương trình sẽ tham gia vào Hành vi không xác định, không có lý do gì để chương trình đưa ra phạm vi phủ định v
. Mặc dù dịch chuyển trái của các giá trị âm được sử dụng để hỗ trợ cho mỗi trình biên dịch có ý nghĩa duy nhất và một lượng lớn mã hiện có phụ thuộc vào hành vi đó, triết học hiện đại sẽ giải thích thực tế rằng Tiêu chuẩn nói rằng các giá trị âm thay đổi trái là UB ngụ ý rằng các nhà văn trình biên dịch nên thoải mái bỏ qua điều đó.
<<
là UB trên các số âm là một cái bẫy nhỏ khó chịu và tôi rất vui khi được nhắc về điều đó!
i+j>k
mang lại 1 hay 0 trong trường hợp bổ sung tràn ra, miễn là nó không có tác dụng phụ nào khác , trình biên dịch có thể thực hiện một số tối ưu hóa lớn không thể thực hiện được nếu lập trình viên viết mã như (int)((unsigned)i+j) > k
.
Tiêu chuẩn C ++ n3337 § 1.3.10 hành vi được xác định thực hiện
hành vi, đối với một chương trình được xây dựng tốt và dữ liệu chính xác, điều đó phụ thuộc vào việc thực hiện và mỗi tài liệu thực hiện
Đôi khi, C ++ Standard không áp đặt hành vi cụ thể cho một số cấu trúc mà thay vào đó nói rằng một hành vi cụ thể, được xác định rõ ràng phải được chọn và mô tả bằng cách triển khai cụ thể (phiên bản của thư viện). Vì vậy, người dùng vẫn có thể biết chính xác chương trình sẽ hoạt động như thế nào mặc dù Standard không mô tả điều này.
Tiêu chuẩn C ++ n3337 § 1.3.24 hành vi không xác định
hành vi mà Tiêu chuẩn quốc tế này áp đặt không có yêu cầu [Lưu ý: Hành vi không xác định có thể được mong đợi khi Tiêu chuẩn quốc tế này bỏ qua bất kỳ định nghĩa rõ ràng nào về hành vi hoặc khi 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 cho phép 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 thuật hoặc thực hiện chương trình theo đặc tính tài liệu của môi trường (có hoặc không có thông báo chẩn đoán), để chấm dứt dịch thuật hoặc thực thi (với việc ban hành của một thông điệp chẩn đoán). Nhiều cấu trúc chương trình sai lầm không gây ra hành vi không xác định; họ được yêu cầu phải được chẩn đoán. - lưu ý cuối]
Khi chương trình gặp phải cấu trúc không được xác định theo Tiêu chuẩn C ++, nó được phép làm bất cứ điều gì nó muốn làm (có thể gửi email cho tôi hoặc có thể gửi email cho bạn hoặc có thể bỏ qua mã hoàn toàn).
Tiêu chuẩn C ++ n3337 § 1.3.25 hành vi không xác định
hành vi, đối với một chương trình được xây dựng tốt và dữ liệu chính xác, điều đó phụ thuộc vào việc thực hiện [Lưu ý: Việc thực hiện là không bắt buộc để ghi lại hành vi nào xảy ra. Phạm vi của các hành vi có thể thường được phân định theo Tiêu chuẩn quốc tế này. - lưu ý cuối]
C ++ Standard không áp đặt hành vi cụ thể cho một số cấu trúc mà thay vào đó nói rằng một hành vi cụ thể, được xác định rõ ràng phải được chọn ( bot không cần mô tả ) bằng cách triển khai cụ thể (phiên bản của thư viện). Vì vậy, trong trường hợp khi không có mô tả nào được cung cấp, người dùng có thể khó biết chính xác chương trình sẽ ứng xử như thế nào.
Xác định thực hiện-
Người triển khai muốn, nên được ghi chép tốt, tiêu chuẩn đưa ra lựa chọn nhưng chắc chắn sẽ biên dịch
Không xác định -
Tương tự như định nghĩa thực hiện nhưng không được ghi lại
Chưa xác định-
Bất cứ điều gì có thể xảy ra, hãy chăm sóc nó.
uint32_t s;
, đánh giá 1u<<s
khi s
33 tuổi có thể được dự kiến có thể mang lại 0 hoặc có thể mang lại 2, nhưng không làm bất cứ điều gì khác lập dị. Tuy nhiên, các trình biên dịch mới hơn, việc đánh giá 1u<<s
có thể khiến trình biên dịch xác định rằng vì s
trước đó phải có ít hơn 32, bất kỳ mã nào trước hoặc sau biểu thức đó chỉ có liên quan nếu s
có từ 32 trở lên có thể bị bỏ qua.