A aritmética sem sinal em C ignora o estouro; ela produz o resultado real módulo a enésima potência de 2, onde n é o número de bits no tipo de dado. Dizemos que ela “trunca” o resultado verdadeiro para os n bits mais baixos.
No parágrafo acima, a palavra módulo significa o operador matemático de cálculo do resto, que em C é representado pelo caractere %. A expressão neste caso poderia ser representada por resultado_real % pow(2, 32), para um inteiro de 32 bits.
Um resultado real que é negativo, quando tomado módulo a enésima potência de 2, gera um número positivo. Por exemplo,
causa estouro porque o número negativo -1 não pode ser armazenado em um tipo sem sinal. O resultado real, que é -1 módulo a enésima potência de 2, é um a menos que a enésima potência de 2. Esse é o maior valor que o tipo de dado sem sinal pode armazenar. Para um unsigned int
de 32 bits, o valor é 4.294.967.295. Veja .
Adicionar esse número a si mesmo, como aqui,
deveria gerar 8.489.934.590; no entanto, isso novamente é grande demais para caber, então o estouro trunca o valor para 4.294.967.294. Se fosse um inteiro com sinal, isso significaria -2, o que (não por coincidência) é igual a -1 + -1.
Para inteiros com sinal, o resultado de um estouro em C é, em princípio, indefinido, o que significa que qualquer coisa pode acontecer. Portanto, compiladores C podem fazer otimizações que tratam o caso de estouro sem qualquer preocupação. (Como o resultado do estouro é indefinido em princípio, não se pode alegar que essas otimizações são errôneas.)
Cuidado: Essas otimizações podem fazer coisas surpreendentes. Por exemplo,
poderia ser otimizado para fazer a atribuição incondicionalmente, porque a condição if
é sempre verdadeira se i + 1
não causar estouro.
O GCC oferece opções do compilador para controlar o tratamento de estouro de inteiros com sinal. Essas opções operam por módulo; ou seja, cada módulo se comporta de acordo com as opções com as quais foi compilado.
Essas duas opções especificam maneiras particulares de lidar com estouro de inteiros com sinal, além da maneira padrão:
-fwrapv
Faz com que operações de inteiros com sinal sejam bem definidas, como operações de inteiros sem sinal: elas produzem os n bits menos significativos do resultado real. O mais alto desses n bits é o bit de sinal do resultado. Com -fwrapv
, essas operações fora do intervalo não são consideradas estouro, então (estritamente falando) o estouro de inteiros nunca acontece.
A opção -fwrapv
habilita algumas otimizações com base nos valores definidos de resultados fora do intervalo. No GCC 8, ela desabilita otimizações que se baseiam em assumir que operações de inteiros com sinal não causarão estouro.
Outra opção útil para encontrar onde ocorre estouro é:
-fsanitize=signed-integer-overflow
Exibe uma mensagem de aviso em tempo de execução quando ocorre estouro de inteiros com sinal. Isso verifica os operadores ‘+’, ‘*’ e ‘-’. Isso tem prioridade sobre -ftrapv
.
-ftrapv
Gera um sinal SIGFPE quando ocorre estouro de inteiros com sinal. Isso termina o programa, a menos que o programa lide com o sinal. Veja .
Quando o valor matemático de uma operação aritmética não cabe no intervalo do tipo de dado em uso, isso é chamado de estouro (overflow, em inglês). Quando isso ocorre na aritmética de inteiros, é chamado de estouro de inteiros.
O estouro de inteiros acontece apenas em operações aritméticas. Operações de conversão de tipo, por definição, não causam estouro, mesmo quando o resultado não cabe em seu novo tipo. .
Números com sinal usam a representação em complemento de dois, na qual o número mais negativo não tem um equivalente positivo (veja ). Assim, o operador unário ‘-’ em um inteiro com sinal pode causar estouro.