C++26: loops infinitos triviais não são mais comportamento indefinido
C++26 altera a forma como a linguagem trata loops infinitos “triviais” como `while (true);`, que antes eram comportamento indefinido e podiam ser otimizados de formas surpreendentes, chegando por vezes a fazer o controlo de execução cair em código não relacionado. Os comentadores discutem se esta regra de longa data alguma vez fez sentido, apontando casos reais de uso em código embedded e de kernel onde estes loops são usados para parar em caso de erro ou esperar por interrupções. A escolha do novo padrão de modelar estes loops como chamadas a `std::this_thread::yield()` é vista por alguns como uma forma pragmática de satisfazer os requisitos de “forward progress” e por outros como uma fonte desnecessária de syscalls ocultas e complexidade numa linguagem de programação de sistemas já sobrecarregada por comportamento indefinido.
Escopo da mudança e comportamento surpreendente
- Muitos comentadores estão chocados com o facto de os loops infinitos triviais serem UB durante “quase 1/6 de um século”, causando coisas como:
- Funções a “cair” para dentro de funções subsequentes quando o compilador remove o loop e o epílogo.
- Funções não-
voidsemreturnsimplesmente a continuar para outro código.
- Alguns consideram isto uma grande violação das expectativas intuitivas de depuração (“remover código até o problema desaparecer” falha se um loop mínimo pode fazer qualquer coisa).
Porque é que loops infinitos eram UB (otimização / forward progress)
- Várias publicações ligam isto a “garantias de forward progress”: os compiladores podem assumir que as threads eventualmente fazem progresso (return, I/O, volatile, atomics).
- UB em loops infinitos permitia:
- Fusão ou reordenação de loops sem provar terminação.
- Transformações como elevar stores acima de computações puras.
- Alguns não estão convencidos de que estas otimizações valham a estranheza semântica, chamando-lhe mais um concurso de benchmarks do que um benefício real para o utilizador.
Casos de uso embarcados e de sistemas
- Forte desacordo sobre “nunca há uma boa razão” para loops infinitos triviais.
- Programadores de embedded e bare-metal citam:
- Handlers de parar-em-erro.
- “Super loops” do
mainem sistemas orientados por interrupções. - Manter-se acordado em vez de transições dispendiosas entre sleep/wake.
- Outros argumentam que se devem usar instruções dedicadas de halt/sleep da CPU ou loops que indiquem erro (por exemplo, códigos de piscar LEDs), e não spins vazios.
Novo comportamento em C++26 (std::this_thread::yield)
- A nova regra define um caso muito estreito: um loop infinitamente vazio e trivial com condição constante.
- As críticas focam-se em:
- Substituí-lo por uma chamada implícita a
this_thread::yield(), ou seja, uma syscall invisível em código que parece não ter efeitos laterais. - Risco em ambientes freestanding / embedded / sandboxed ou em contextos de tempo real.
- Inconsistência:
while(true);vswhile(true) continue;vs outras formas terem semânticas diferentes.
- Substituí-lo por uma chamada implícita a
- Alguns preferem isto a UB, mas ainda assim chamam-lhe “horrível” e prefeririam ver:
- Um diagnóstico/warning.
- Um primitivo da standard library do tipo “halt aqui”.
- Ou a regra ao estilo C11 (loops infinitos com condição constante são simplesmente definidos como não terminando).
Preocupações mais amplas sobre UB e a direção do C++
- Longo subthread sobre UB como ferramenta para otimização versus fonte de miscompilações e comportamento incompreensível.
- Comparações com C e Rust:
- O tratamento especial de loops em expressão constante em C é visto como mais sensato.
loop {}em Rust e a história do LLVM são mencionados como pressão anterior para corrigir isto.
- Vários comentários lamentam o comportamento oculto e a complexidade crescentes no C++ moderno, e expressam receio de que o uso freestanding / embedded continue a ficar mais difícil.