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.
NgRx 22 lança Resource Extensions para evitar reset de estado no resource API do Angular
A nova versão da biblioteca de estado para Angular adiciona extensões experimentais que mantêm o value() de um resource durante reload e impedem que erros quebrem o SignalStore, além de signals dinâmicos para tipos union.














