C++26 acaba com UB em loops infinitos triviais
O padrão C++26 torna bem definidos loops como while(true); antes classificados como comportamento indefinido, um problema que já causou remoção de código de tratamento de erro em builds otimizados.

O padrão C++26 torna bem definidos loops como while(true); antes classificados como comportamento indefinido, um problema que já causou remoção de código de tratamento de erro em builds otimizados.
Até o C++23, um trecho tão simples quanto while (true); era, tecnicamente, comportamento indefinido (undefined behaviour, ou UB). C++26 fecha essa lacuna através da proposta P2809R3, como detalhou o desenvolvedor Sandor Dargo em seu blog. A mudança parece cosmética à primeira vista, mas resolve um problema real que já derrubou lógica de tratamento de erro em sistemas embarcados.
O bug que não era bug
O artigo de Dargo reproduz um exemplo no Compiler Explorer que mostra o estrago na prática: um main() com um while (true); vazio seguido de uma função unreachable() que imprime "Hello world!". Em teoria, essa função nunca deveria ser chamada, o loop nunca termina. Mas ao compilar com Clang, o programa imprime a mensagem.
// https://godbolt.org/z/WYMxxeW1T
#include <iostream>
int main() {
while (true)
;
}
void unreachable() {
std::cout << "Hello world!" << std::endl;
}O motivo é a regra de forward progress guarantee, introduzida no C++11 junto com o suporte a threads. A norma ([intro.progress]) permite que a implementação assuma que qualquer thread eventualmente vai terminar, chamar uma função de I/O de biblioteca, acessar um glvalue volatile, ou executar uma operação de sincronização ou atômica. Um while (true); sem corpo não faz nada disso. Logo, sob as regras pré-C++26, uma execução que fica presa nesse loop para sempre tem comportamento indefinido, e o otimizador fica livre para assumir que a execução nunca fica presa ali, removendo o loop e tratando o trecho seguinte como alcançável.
Dargo aponta que essa não é falha de compilador: é exatamente o que a norma permitia. "This is not a compiler bug, it's just UB, still better than nasal demons", escreve ele, referenciando a expressão clássica da comunidade C/C++↳C++6 conteúdosSão Paulo recebe encontro de Programadores C & C++Dev (Back & Front) · mai 2019Melhores linguagens de programação para desenvolver jogosMarketing Tech · nov 2024Desmistificando Rust: a linguagem segura e rápida que você precisa conhecerDev (Back & Front) · out 2024Ver tudo em Dev (Back & Front) → para descrever as consequências extremas de UB.
Por que alguém escreveria isso de propósito
O ponto central do artigo é que while (true); não é código acidental, ruim ou raro. É um padrão comum em firmware, drivers e código de kernel para tratar erro fatal quando não existe um sistema operacional para o qual retornar:
if (hardware_init_failed()) {
log_error("fatal: hardware init failed");
while (true)
; // halt - there's nothing left to do
}O problema, segundo Dargo, é que esse handler de erro fatal, escrito para simplesmente travar o dispositivo, também caía sob a mesma UB. Um otimizador agressivo podia remover o loop e deixar a execução cair no código que o linker posicionou logo depois, exatamente como no exemplo do "Hello world!". Em um sistema embarcado real, isso significa que o handler de erro fatal não trava o hardware: o dispositivo continua rodando em estado corrompido, executando qualquer instrução que vier a seguir. Em código crítico de segurança, isso é uma vulnerabilidade concreta, não uma curiosidade acadêmica.
A divergência histórica com o C
Um detalhe que o artigo destaca é que o C já resolvia isso corretamente desde o C11. C++11 e C11 introduziram regras de forward progress na mesma época, mas o C incluiu uma regra a mais: loops cuja expressão de controle é uma constant expression não podem ser assumidos como terminantes. Por isso, while (1); sempre foi bem definido em C. O C++ nunca adotou essa regra extra, criando uma divergência desnecessária entre as duas linguagens: o mesmo código, while (1);, era seguro em C e UB em C++.
O que exatamente o C++26 define
P2809R3 não copia a regra do C de forma ampla, o que poderia inibir otimizações legítimas em outros tipos de loop. Em vez disso, a proposta cria uma categoria estreita chamada trivial infinite loop, definida por duas condições simultâneas:
- O loop precisa ser um trivially empty iteration statement: o corpo precisa ser literalmente vazio (
;ou{}). Qualquer instrução no corpo, mesmo uma expression statement sem efeito como"a string";, desqualifica o loop. - A expressão de controle precisa ser uma constant expression que avalia para
true. Em umforsem condição, otrueé implícito.
A tabela do artigo original mostra exemplos concretos do que se qualifica:
| Código | Trivial infinite loop? | |---|---| | while (true); | Sim | | for (;;); | Sim | | do {} while (true); | Sim | | constexpr bool go = true; while (go); | Sim, go é constant expression | | while (true) { "a string"; } | Não, corpo tem instrução | | while (true) if (done) break; | Não, corpo não é vazio | | bool done = false; while (!done); | Não, não é constant expression |
Quando as duas condições são atendidas, o corpo do loop é substituído, semanticamente, por uma chamada a std::this_thread::yield(). Isso dá à execução do loop a semântica de forward progress que faltava antes, e o otimizador deixa de poder tratar esse loop específico como UB.
A ressalva para freestanding
Para implementações freestanding, ou seja, sem sistema operacional embaixo, típicas de firmware e bare metal, a substituição por std::this_thread::yield() fica implementation-defined: o compilador pode ou não aplicá-la. Isso importa justamente para o caso de uso que motivou a mudança: transformar um loop de halt deliberado em um yield cooperativo poderia introduzir comportamento que o programador de sistema embarcado nunca pretendeu. A norma deixa essa decisão para cada implementação de toolchain freestanding.
O que muda para quem programa em C++ hoje
Vale notar que P2809R3 foi aceita também como defect report, o que significa que compiladores podem retroaplicar a correção a modos de linguagem anteriores. Na prática, isso quer dizer que quem tentar reproduzir o comportamento antigo (o "Hello world!" impossível) em um compilador recente pode não conseguir, mesmo compilando em modo C++20, porque o fix já pode estar presente no compilador nesse modo.
Para quem mantém código de baixo nível, o recado prático é: handlers de erro fatal escritos como while (true); deixam de depender de UB para funcionar como halt, o que é uma boa notícia para revisões de segurança em firmware. Já para quem lida com concorrência e espera que um yield explícito aconteça em loops de espera ativa, vale checar a documentação do compilador sobre o comportamento em builds freestanding antes de assumir que a substituição por std::this_thread::yield() está de fato acontecendo.
Fonte: Hacker News
Este artigo foi escrito por Redação iMasters. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.













