Chỉ cần nêu vấn đề, Vấn đề Nguy hiểm khác là một sự mơ hồ trong đặc tả cú pháp mã trong đó có thể không rõ ràng, trong trường hợp ifs và elses tiếp theo, cái nào khác thuộc về if.
Ví dụ đơn giản và cổ điển nhất:
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
Không rõ ràng, với những người không biết chi tiết cụ thể của đặc tả ngôn ngữ, điều này có if
được else
(và đoạn mã cụ thể này có giá trị trong nửa tá ngôn ngữ, nhưng có thể thực hiện khác nhau ở mỗi ngôn ngữ).
Cấu trúc Dangling Else đặt ra một vấn đề tiềm ẩn đối với việc triển khai trình phân tích cú pháp không quét, bởi vì chiến lược này sẽ làm xáo trộn luồng tệp một ký tự tại một thời điểm, cho đến khi trình phân tích cú pháp thấy rằng nó đủ để token hóa (tiêu hóa vào ngôn ngữ lắp ráp hoặc ngôn ngữ trung gian mà nó đang biên dịch) . Điều này cho phép trình phân tích cú pháp duy trì trạng thái tối thiểu; ngay khi nó nghĩ rằng nó có đủ thông tin để viết mã thông báo mà nó đã phân tích vào tệp, nó sẽ làm như vậy. Đó là mục tiêu cuối cùng của một trình phân tích cú pháp không quét; biên soạn nhanh, đơn giản, gọn nhẹ.
Giả sử dòng mới và khoảng trắng trước hoặc sau dấu chấm câu là vô nghĩa (vì chúng có trong hầu hết các ngôn ngữ kiểu C), câu lệnh này sẽ xuất hiện cho trình biên dịch dưới dạng:
if(conditionA)if(conditionB)doFoo();else doBar;
Phân tích cú pháp hoàn hảo cho máy tính, vì vậy hãy xem. Tôi nhận được một nhân vật tại một thời điểm cho đến khi tôi có:
if(conditionA)
Ồ, tôi biết điều đó có nghĩa là gì (trong C #), nó có nghĩa là " push
điều kiệnA lên ngăn xếp eval và sau đó gọi brfalse
để chuyển đến câu lệnh sau dấu chấm phẩy tiếp theo nếu nó không đúng". Ngay bây giờ tôi không thấy dấu chấm phẩy, vì vậy bây giờ tôi sẽ đặt phần bù nhảy của mình sang khoảng trống tiếp theo sau hướng dẫn này và tôi sẽ tăng phần bù đó khi tôi chèn thêm hướng dẫn cho đến khi tôi thấy dấu chấm phẩy. Tiếp tục phân tích ...
if(conditionB)
OK, điều này phân tích ra một cặp thao tác IL tương tự, và nó diễn ra ngay sau lệnh tôi vừa phân tích. Tôi không thấy dấu chấm phẩy, vì vậy tôi sẽ tăng độ lệch của câu lệnh trước bằng độ dài của hai lệnh (một cho đẩy và một cho ngắt) và tiếp tục tìm kiếm.
doFoo();
Ok, thật dễ dàng. Đó là " call
doFoo". Và đó có phải là dấu chấm phẩy mà tôi thấy không? Chà, thật tuyệt, đó là kết thúc của dòng. Tôi sẽ tăng số lần nhảy của cả hai khối của mình theo độ dài của hai lệnh này và quên tôi đã từng quan tâm. OK, tiếp tục ...
else
... Uh-oh. Điều này không đơn giản như nó nhìn. OK, tôi đã quên những gì tôi vừa làm, nhưng else
có nghĩa là có một tuyên bố phá vỡ có điều kiện ở đâu đó mà tôi đã thấy, vì vậy hãy để tôi nhìn lại ... vâng, đó là brfalse
, ngay sau khi tôi nhấn một số "conditionB" ngăn xếp, bất cứ điều gì đã được. OK, bây giờ tôi cần một điều kiện vô điều kiện break
như tuyên bố tiếp theo. Tuyên bố sẽ được đưa ra sau đó chắc chắn là mục tiêu phá vỡ có điều kiện của tôi, vì vậy tôi sẽ đảm bảo rằng tôi có quyền và tôi sẽ tăng thời gian nghỉ vô điều kiện mà tôi đã đưa vào.
doBar();
Điều đó thật dễ dàng. " call
DoBar". Và có một dấu chấm phẩy, và tôi chưa bao giờ thấy bất kỳ dấu ngoặc nhọn nào. Vì vậy, vô điều kiện break
nên chuyển sang tuyên bố tiếp theo, bất kể đó là gì, và tôi có thể quên tôi từng quan tâm.
Vì vậy, chúng ta có gì ... (lưu ý: bây giờ là 10:00 và tôi không cảm thấy muốn chuyển đổi các bit bit thành thập lục phân hoặc điền vào toàn bộ vỏ IL của một hàm bằng các lệnh này, vì vậy đây chỉ là giả IL sử dụng số dòng trong đó thường có các byte bù):
ldarg.1 //conditionA
brfalse <line 6> //jumps to "break"
ldarg.2 //conditionB
brfalse <line 7> //jumps to "call doBar"
call doFoo
break <line 8> //jumps beyond statement in scope
call doBar
<line 8 is here>
Chà, điều đó thực sự thực thi chính xác, NẾU quy tắc (như trong hầu hết các ngôn ngữ kiểu C) là else
đi với gần nhất if
. Được thụt lề để theo dõi lồng nhau, nó sẽ thực thi như thế này, trong đó nếu điều kiệnA là sai, toàn bộ phần còn lại của đoạn mã sẽ bị bỏ qua:
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
... nhưng nó làm như vậy bởi sự ngẫu nhiên, bởi vì ngắt kết hợp với if
câu lệnh bên ngoài nhảy đến break
câu lệnh ở cuối phần bên trong if
, đưa con trỏ thực thi vượt ra ngoài toàn bộ câu lệnh. Đó là một bước nhảy không cần thiết thêm, và nếu ví dụ này phức tạp hơn nữa thì nó có thể không còn hoạt động nếu được phân tích cú pháp và mã hóa theo cách này.
Ngoài ra, điều gì sẽ xảy ra nếu đặc tả ngôn ngữ nói rằng sự lơ lửng else
thuộc về đầu tiên if
và nếu điều kiệnA là sai thì doBar được thực thi, trong khi nếu điều kiệnA là đúng nhưng không phải điều kiệnB thì không có gì xảy ra, như vậy?
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
Trình phân tích cú pháp đã quên lần đầu tiên if
tồn tại và vì vậy thuật toán trình phân tích cú pháp đơn giản này sẽ không tạo ra mã chính xác, không nói gì về mã hiệu quả.
Bây giờ, trình phân tích cú pháp có thể đủ thông minh để ghi nhớ các if
s và else
nó tồn tại trong một thời gian dài hơn, nhưng nếu thông số ngôn ngữ nói một else
sau hai if
s khớp với đầu tiên if
, điều đó gây ra vấn đề với hai if
s với khớp else
s:
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
else
doBaz();
Trình phân tích cú pháp sẽ thấy cái đầu tiên else
, khớp với cái đầu tiên if
, sau đó nhìn thấy cái thứ hai và rơi vào trạng thái hoảng loạn "cái quái gì tôi đang làm lại". Tại thời điểm này, các trình phân tích cú pháp đã nhận được khá nhiều mã ở trạng thái có thể thay đổi mà nó sẽ thay vào đó là đẩy ra đoạn phim đầu ra.
Có giải pháp cho tất cả những vấn đề này và what-ifs. Nhưng, hoặc mã cần thiết là thông minh đó làm tăng độ phức tạp của thuật toán trình phân tích cú pháp hoặc thông số ngôn ngữ cho phép trình phân tích cú pháp này làm tăng mức độ dài của mã nguồn ngôn ngữ, chẳng hạn như bằng cách yêu cầu các câu lệnh kết thúc như end if
, hoặc dấu ngoặc chỉ ra lồng nhau chặn nếu if
câu lệnh có một else
(cả hai thường được thấy trong các kiểu ngôn ngữ khác).
Đây chỉ là một ví dụ đơn giản về một vài if
câu lệnh và xem xét tất cả các quyết định mà trình biên dịch phải đưa ra, và dù sao nó cũng có thể rất dễ bị nhầm lẫn. Đây là chi tiết đằng sau câu nói vô thưởng vô phạt đó từ Wikipedia trong câu hỏi của bạn.