Este é UB; em termos de ISO C ++, todo o comportamento de todo o programa é completamente não especificado para uma execução que eventualmente atinge o UB. O exemplo clássico é que, na medida em que o padrão C ++ se importa, ele pode fazer com que os demônios voem do seu nariz. (Eu recomendo não usar uma implementação em que os demônios nasais são uma possibilidade real). Veja outras respostas para mais detalhes.
Os compiladores podem "causar problemas" em tempo de compilação para os caminhos de execução que eles podem ver levando a UB visível em tempo de compilação, por exemplo, suponha que esses blocos básicos nunca sejam atingidos.
Consulte também O que todo programador C deve saber sobre comportamento indefinido (blog do LLVM). Conforme explicado lá, o UB de estouro assinado permite que os compiladores provem que os for(... i <= n ...)
loops não são infinitos, mesmo para desconhecidos n
. Ele também permite que eles "promovam" contadores de loop int para a largura do ponteiro, em vez de refazer a extensão do sinal. (Portanto, a conseqüência do UB nesse caso poderia ser acessar fora dos elementos baixos de 64k ou 4G de uma matriz, se você estivesse esperando uma quebra automática assinada i
em seu intervalo de valores.)
Em alguns casos, os compiladores emitem uma instrução ilegal como x86 ud2
para um bloco que provavelmente causa o UB, se alguma vez for executado. (Observe que uma função nem sempre pode ser chamada, portanto, em geral, os compiladores não podem ficar furiosos e interromper outras funções, ou mesmo caminhos possíveis através de uma função que não atinge UB. todas as entradas que não levam ao UB.)
Provavelmente, a solução mais eficiente é descascar manualmente a última iteração para factor*=10
evitar o desnecessário .
int result = 0;
int factor = 1;
for (... i < n-1) { // stop 1 iteration early
result = ...
factor *= 10;
}
result = ... // another copy of the loop body, using the last factor
// factor *= 10; // and optimize away this dead operation.
return result;
Ou se o corpo do loop for grande, considere simplesmente usar um tipo não assinado para factor
. Em seguida, você pode deixar o multiplicador sem sinal estourar e ele fará um empacotamento bem definido com uma potência de 2 (o número de bits de valor no tipo não assinado).
Isso é bom mesmo se você usá-lo com tipos assinados, especialmente se sua conversão não assinada-> nunca exceder.
A conversão entre o complemento não assinado e o do 2 assinado é livre (o mesmo padrão de bits para todos os valores); o módulo para int -> unsigned especificado pelo padrão C ++ simplifica o uso do mesmo padrão de bits, diferente do complemento ou sinal / magnitude.
E não assinado-> assinado é igualmente trivial, embora seja definido pela implementação para valores maiores que INT_MAX
. Se você não estiver usando o enorme resultado não assinado da última iteração, não terá com que se preocupar. Mas, se estiver, consulte A conversão de não assinado em assinado é indefinida? . O caso do valor não se encaixa é definido pela implementação , o que significa que uma implementação deve escolher algum comportamento; os sãos apenas truncam (se necessário) o padrão de bits não assinado e o usam como assinado, porque isso funciona para valores dentro do intervalo da mesma maneira, sem trabalho extra. E definitivamente não é UB. Assim, grandes valores não assinados podem se tornar inteiros com sinal negativo. por exemplo, depois de int x = u;
gcc e clang não otimizarx>=0
como sempre sendo verdade, mesmo sem -fwrapv
, porque eles definiram o comportamento.