Estouro de número inteiro não assinado é bem definido pelos padrões C e C ++. Por exemplo, o padrão C99 ( §6.2.5/9
) declara
Uma computação envolvendo operandos não assinados nunca pode transbordar, porque um resultado que não pode ser representado pelo tipo inteiro não assinado resultante é reduzido pelo módulo, o número que é um maior que o maior valor que pode ser representado pelo tipo resultante.
No entanto, ambos os padrões afirmam que o excesso de número inteiro assinado é um comportamento indefinido. Mais uma vez, do padrão C99 ( §3.4.3/1
)
Um exemplo de comportamento indefinido é o comportamento no overflow de número inteiro
Existe uma razão histórica ou (ainda melhor!) Técnica para essa discrepância?
c++
c
undefined-behavior
integer-overflow
Anthony Vallée-Dubois
fonte
fonte
if (a + b < a)
). O estouro na multiplicação é difícil para os tipos assinado e não assinado.MAX_INT+1 == -0
, enquanto em um de complemento de dois seriaINT_MIN
Respostas:
O motivo histórico é que a maioria das implementações de C (compiladores) apenas usava qualquer comportamento de estouro mais fácil de implementar com a representação inteira usada. As implementações C usavam geralmente a mesma representação usada pela CPU - portanto, o comportamento de estouro seguia da representação inteira usada pela CPU.
Na prática, são apenas as representações dos valores assinados que podem diferir de acordo com a implementação: complemento de alguém, complemento de dois, magnitude de sinal. Para um tipo não assinado, não há razão para o padrão permitir variação, porque existe apenas uma representação binária óbvia (o padrão permite apenas representação binária).
Citações relevantes:
C99 6.2.6.1:3 :
C99 6.2.6.2:2 :
Atualmente, todos os processadores usam a representação de complemento de dois, mas o estouro aritmético assinado permanece indefinido e os fabricantes de compiladores querem que ele permaneça indefinido, porque usam essa indefinição para ajudar na otimização. Veja, por exemplo, este post de Ian Lance Taylor ou esta reclamação de Agner Fog e as respostas para o relatório de erros.
fonte
Além da boa resposta de Pascal (que tenho certeza de que é a principal motivação), também é possível que alguns processadores causem uma exceção no estouro de número inteiro assinado, o que obviamente causaria problemas se o compilador tivesse que "providenciar outro comportamento" ( por exemplo, use instruções extras para verificar se há um potencial estouro e calcular de maneira diferente nesse caso).
Também é importante notar que "comportamento indefinido" não significa "não funciona". Isso significa que a implementação está autorizada a fazer o que quiser nessa situação. Isso inclui fazer "a coisa certa", bem como "chamar a polícia" ou "bater". A maioria dos compiladores, quando possível, escolhe "fazer a coisa certa", assumindo que é relativamente fácil de definir (nesse caso, é). No entanto, se você estiver tendo estouros excessivos nos cálculos, é importante entender o que realmente resulta e que o compilador PODE fazer algo diferente do que você espera (e que isso pode depender muito da versão do compilador, configurações de otimização, etc.) .
fonte
int f(int x) { return x+1>x; }
com otimização. O GCC e o ICC, com opções padrão, otimizam o acima parareturn 1;
.int
estouro, dependendo dos níveis de otimização, consulte ideone.com/cki8nM. Acho que isso demonstra que sua resposta oferece maus conselhos.Antes de tudo, observe que o C11 3.4.3, como todos os exemplos e notas de rodapé, não é um texto normativo e, portanto, não é relevante para citar!
O texto relevante que afirma que o excesso de números inteiros e flutuantes é um comportamento indefinido é o seguinte:
C11 6.5 / 5
Um esclarecimento sobre o comportamento de tipos inteiros não assinados especificamente pode ser encontrado aqui:
C11 6.2.5 / 9
Isso torna os tipos inteiros não assinados um caso especial.
Observe também que há uma exceção se qualquer tipo for convertido em um tipo assinado e o valor antigo não puder mais ser representado. O comportamento é então meramente definido pela implementação, embora um sinal possa ser gerado.
C11 6.3.1.3
fonte
Além dos outros problemas mencionados, ter quebra de linha não assinada faz com que os tipos inteiros não assinados se comportem como grupos algébricos abstratos (o que significa que, entre outras coisas, para qualquer par de valores
X
eY
, haverá algum outro valorZ
queX+Z
, se for convertido corretamente , igualY
eY-Z
, se convertido corretamente, igualX
) Se os valores não assinados fossem meramente tipos de local de armazenamento e não tipos de expressão intermediária (por exemplo, se não houvesse equivalente não assinado do maior tipo inteiro, e operações aritméticas em tipos não assinados se comportassem como se os tivessem convertido primeiro em tipos assinados maiores, então não haveria tanta necessidade de um comportamento de quebra definido, mas é difícil fazer cálculos em um tipo que não tenha, por exemplo, um inverso aditivo.Isso ajuda em situações em que o comportamento wrap-around é realmente útil - por exemplo, com números de sequência TCP ou determinados algoritmos, como cálculo de hash. Também pode ajudar em situações em que é necessário detectar estouro, já que executar cálculos e verificar se estouraram é geralmente mais fácil do que verificar antecipadamente se estourariam, especialmente se os cálculos envolverem o maior tipo inteiro disponível.
fonte
a+b-c
é calculada dentro de um loop, masb
ec
são constantes dentro desse loop, ele pode ser útil para mover cálculo de(b-c)
fora do loop, mas fazer isso exigiria, entre outras coisas, que(b-c)
produzem um valor que, quando adicionado aa
, produziráa+b-c
, o que por sua vez requer quec
tenha um aditivo inverso.(a+b)-c
igual à representatividadea+(b-c)
ou não do valor aritméticob-c
dentro do tipo, a substituição será válida independentemente do intervalo de valores possível para(b-c)
.Talvez outro motivo para a definição da aritmética não assinada seja porque os números não assinados formam números inteiros módulo 2 ^ n, onde n é a largura do número não assinado. Os números não assinados são simplesmente números inteiros representados usando dígitos binários em vez de dígitos decimais. A realização das operações padrão em um sistema de módulo é bem compreendida.
A citação do OP se refere a esse fato, mas também destaca o fato de que há apenas uma maneira lógica e inequívoca de representar números inteiros não assinados em binário. Por outro lado, os números assinados são representados com mais freqüência usando o complemento de dois, mas outras opções são possíveis, conforme descrito na norma (seção 6.2.6.2).
A representação de complemento do Two permite que certas operações façam mais sentido no formato binário. Por exemplo, incrementar números negativos é o mesmo que para números positivos (esperados sob condições de estouro). Algumas operações no nível da máquina podem ser as mesmas para números assinados e não assinados. No entanto, ao interpretar o resultado dessas operações, alguns casos não fazem sentido - excesso positivo e negativo. Além disso, os resultados do estouro diferem dependendo da representação assinada subjacente.
fonte