Apenas para declarar o problema, o Dangling Else Problem é uma ambiguidade na especificação da sintaxe do código, onde pode não ser claro, nos casos de ifs e elses seguidos, o que mais pertence a qual if.
O exemplo mais simples e clássico:
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
Não está claro para quem não conhece as especificidades da especificação de idioma de cor, que if
recebe o else
(e esse trecho de código específico é válido em meia dúzia de idiomas, mas pode ter um desempenho diferente em cada um).
A construção Dangling Else apresenta um problema em potencial para implementações de analisador sem scanner, porque a estratégia é reduzir o fluxo de arquivos um caractere de cada vez, até que o analisador veja que tem o suficiente para tokenizar (digerir no assembly ou no idioma intermediário que está compilando) . Isso permite que o analisador mantenha o estado mínimo; assim que achar que possui informações suficientes para gravar os tokens analisados no arquivo, isso será feito. Esse é o objetivo final de um analisador sem scanner; compilação rápida, simples e leve.
Supondo que novas linhas e espaços em branco antes ou depois da pontuação não tenham sentido (como na maioria dos idiomas do estilo C), essa declaração apareceria para o compilador como:
if(conditionA)if(conditionB)doFoo();else doBar;
Perfeitamente analisável para um computador, então vamos ver. Eu recebo um personagem de cada vez até ter:
if(conditionA)
Ah, eu sei o que isso significa (em C #), significa " push
condiçãoA na pilha de avaliação e, em seguida, chamo brfalse
para pular para a instrução após o próximo ponto e vírgula, se não for verdade". No momento, não vejo ponto e vírgula, portanto, por enquanto, definirei meu deslocamento de salto para o próximo espaço após esta instrução e aumentarei esse deslocamento à medida que insiro mais instruções até ver um ponto e vírgula. Continuando a analisar ...
if(conditionB)
OK, isso analisa um par semelhante de operações de IL e segue imediatamente após a instrução que acabei de analisar. Como não vejo ponto-e-vírgula, aumentarei o deslocamento da minha declaração anterior pelo comprimento dos meus dois comandos (um para o push e outro para o break) e continuarei procurando.
doFoo();
Ok, é fácil. Isso é " call
doFoo". E isso é um ponto e vírgula que eu vejo? Bem, isso é ótimo, esse é o fim da linha. Vou incrementar as compensações de pulo de meus dois blocos pelo comprimento desses dois comandos e esquecer que alguma vez me importei. OK, seguindo em frente ...
else
... Uh-oh. Isso não é tão simples quanto parecia. OK, esqueci o que estava fazendo, mas isso else
significa que há uma declaração de interrupção condicional em algum lugar que eu já vi, então deixe-me olhar para trás ... sim, aí está brfalse
, logo após pressionar alguma "condiçãoB" em a pilha, o que quer que fosse. OK, agora eu preciso de um incondicional break
como a próxima declaração. A declaração que virá depois disso agora é definitivamente o objetivo da minha interrupção condicional, por isso vou me certificar de que ela esteja certa e aumentarei a interrupção incondicional que eu introduzi. Seguindo em frente ...
doBar();
Isso é fácil. " call
doBar". E há um ponto e vírgula, e eu nunca vi aparelho. Portanto, o incondicional break
deve passar para a próxima declaração, seja ela qual for, e posso esquecer que alguma vez me importei.
Então, o que temos ... (nota: são 22h e não tenho vontade de converter deslocamentos de bits em hexadecimal ou preencher o shell IL completo de uma função com esses comandos, então isso é apenas pseudo-IL usando números de linha onde normalmente haveria desvios de bytes):
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>
Bem, isso realmente é executado corretamente, SE a regra (como na maioria das linguagens de estilo C) é else
a mais próxima if
. Recuado para seguir o aninhamento da execução, ele seria executado assim, onde, se conditionA for false, o restante do snippet inteiro será ignorado:
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
... mas o faz por acaso, porque a quebra associada à if
instrução externa salta para a break
instrução no final do interno if
, o que leva o ponteiro da execução além da instrução inteira. É um salto extra desnecessário e, se este exemplo for mais complexo, poderá não funcionar mais se analisado e tokenizado dessa maneira.
Além disso, e se a especificação da linguagem disser que um dangling else
pertence ao primeiro if
e se a condiçãoA for falsa, o doBar será executado, enquanto que se a condiçãoA for verdadeira, mas não a condiçãoB, nada acontecerá, como isso?
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
O analisador havia esquecido o primeiro if
que existia e, portanto, esse algoritmo simples de analisador não produzia o código correto, para não falar em código eficiente.
Agora, o analisador pode ser inteligente o suficiente para lembrar os se if
e else
s por mais tempo, mas se a especificação do idioma indicar um único else
depois de dois if
s corresponder ao primeiro if
, isso causará um problema com dois if
s com else
s correspondentes :
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
else
doBaz();
O analisador verá o primeiro else
, corresponderá ao primeiro if
, depois verá o segundo e entrará em pânico no modo "que diabos eu estava fazendo de novo"? Nesse ponto, o analisador obteve bastante código em um estado mutável que preferiria já ter enviado para o fluxo de arquivos de saída.
Existem soluções para todos esses problemas e what-ifs. Porém, o código necessário para ser inteligente aumenta a complexidade do algoritmo do analisador ou a especificação de idioma que permite que o analisador seja burro aumenta a verbosidade do código-fonte do idioma, como exigir instruções finais como end if
parênteses indicando colchetes bloqueia se a if
instrução tiver um else
(ambos os quais são comumente vistos em outros estilos de idioma).
Este é apenas um exemplo simples de algumas if
declarações, e observe todas as decisões que o compilador teve que tomar e onde ele poderia facilmente ter estragado tudo. Este é o detalhe por trás dessa declaração inócua da Wikipedia em sua pergunta.