Тільки для того, щоб заявити про проблему, проблема "Dangling Else" - це неоднозначність у специфікації синтаксису коду, де вона може бути незрозумілою, у випадках, якщо вони є нерозбірливими ifs та elses, до яких ще належить, якщо якщо.
Найпростіший і класичний приклад:
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
Незрозуміло тим, хто не знає специфіки специфікації мови напам'ять, яка if
отримує else
(і цей конкретний фрагмент коду дійсний на півдесятка мов, але може працювати по-різному).
Конструкція Dangling Else створює потенційну проблему для реалізацій парсерного сканування, оскільки стратегія полягає в перекручуванні файлового потоку по одному символу за один раз, поки аналізатор не побачить, що його достатньо для токенізації (дайджест на збірну або проміжну мову, яку він збирає) . Це дозволяє парсеру підтримувати мінімальний стан; як тільки він вважає, що має достатньо інформації для написання маркерів, які він розбирає у файл, він зробить це. Ось кінцева мета аналізатора без сканерів; швидка, проста, легка компіляція.
Якщо припустити, що нові рядки та пробіли до або після пунктуації є безглуздими (як це є у більшості мов стилю С), це твердження буде видаватися компілятору як:
if(conditionA)if(conditionB)doFoo();else doBar;
Ідеально розбирається на комп’ютері, тому подивимось. Я отримую один символ за один раз, поки у мене є:
if(conditionA)
О, я знаю, що це означає (у C #), це означає " push
conditionA на eval стек, а потім заклик brfalse
перейти до заяви після наступної крапки з комою, якщо це неправда". Зараз я не бачу крапки з комою, тому поки що після цієї інструкції я встановлю зміщення переходу до наступного пробілу, і збільшу це зміщення, коли я вставляю більше інструкцій, поки не побачу крапку з комою. Продовжуючи розбирати ...
if(conditionB)
Гаразд, це аналізує аналогічну пару операцій ІЛ, і це відбувається відразу після інструкції, яку я щойно проаналізував. Я не бачу крапки з комою, тому я збільшить зсув мого попереднього твердження на довжину моїх двох команд (одна для натискання та одна для перерви) і продовжую шукати.
doFoo();
Гаразд, це просто. Це " call
doFoo". І це крапка з комою я бачу? Ну, це чудово, ось і кінець рядка. Я збільшить зсув обох своїх блоків по довжині цих двох команд і забуду, що я коли-небудь хвилювався. Добре, рухаємось далі ...
else
... Ой-ой. Це не так просто, як виглядало. Гаразд, я забув, що я тільки робив, але else
означає, що десь я вже бачив умовний перерву, який я вже бачив, тому дозвольте оглянутись назад… так, так це brfalse
, одразу після того, як я натискаю на деякий «умоваB» на стек, що б там не було. Гаразд, зараз мені потрібен безумовний, break
як наступне твердження. Заява, яка надійде після цього, тепер, безумовно, є цільовою умовою перерви, тому я переконуюсь, що я маю це правильно, і збільшу безумовну перерву, яку я вклав. Переміщуючись далі ...
doBar();
Це легко. " call
doBar". І є крапка з комою, і я ніколи не бачив дужок. Отже, безумовне break
повинно перейти до наступного твердження, яке б воно не було, і я можу забути, що я коли-небудь хвилювався.
Отже, що ми маємо ... (зауважте: це 10:00 вечора, і мені не здається перетворювати зсуви бітів у шістнадцятковий чи заповнювати повну оболонку IL функції за допомогою цих команд, тож це просто псевдо-IL використовуючи номери рядків, де зазвичай є зміщення байтів):
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>
Добре, що насправді виконується правильно, ЯКЩО правило (як і в більшості мов стилю C) полягає в тому, що else
йдеться з найближчими if
. Відступ для наступного введення в виконання, воно буде виконуватись так, якщо, якщо умоваA є помилковою, весь решта фрагмента пропускається:
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
... але це робиться через serendipity, тому що розрив, пов'язаний із зовнішнім if
твердженням, переходить до break
оператора в кінці внутрішнього if
, який виводить покажчик виконання за межі всього оператора. Це зайвий непотрібний стрибок, і якби цей приклад був ще складнішим, він би більше не функціонував, якщо проаналізувати та токенізувати таким чином.
Крім того, що робити, якщо мовна специфікація сказала, що звисання else
належить першому if
, а якщо умоваA - помилковою, то виконується doBar, тоді як якщо умоваA - це правда, але не умоваB, тоді нічого не відбувається, як це?
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
Парсер забув перший if
існував і тому цей простий алгоритм аналізатора не дасть правильного коду, не кажучи вже про ефективний код.
Тепер, аналізатор міг би бути досить розумним, щоб запам'ятати if
s і else
s, які він має довший час, але якщо мовна специфікація говорить сингл else
після двох if
s збігів з першою if
, це спричиняє проблеми з двома if
s зі збігом else
s:
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
else
doBaz();
Парсер побачить перше else
, збігться з першим if
, потім побачить другий і впаде в паніку "що, чорт забираю, я робив знову". У цей момент аналізатор отримав досить багато коду в зміненому стані, що він, скоріше, вже витіснив би вихідний потік файлів.
У всіх цих проблемах є рішення та що - якщо. Але або код, необхідний для розумного, збільшує складність алгоритму аналізатора, або мовна специфікація, що дозволяє аналізатору бути таким німим, збільшує багатослівність мовного вихідного коду, наприклад, вимагаючи закінчення операторів типу end if
, або дужки із зазначенням вкладених блокує, якщо у if
висловлюванні є else
(обидва вони зазвичай бачать в інших мовних стилях).
Це лише один, простий приклад пари if
тверджень, і подивіться на всі рішення, які повинен був прийняти компілятор, і де він міг би дуже легко зіпсуватися. Це деталь, що стоїть за цією нешкідливою заявою з Вікіпедії у вашому запитанні.