Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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.
-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 .
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
.
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.
Os operadores aritméticos em C tentam ser o mais semelhante possível às operações aritméticas abstratas, mas é impossível fazer isso de forma perfeita. Números em um computador têm um intervalo finito de valores possíveis, e valores não inteiros têm um limite em sua precisão possível. No entanto, exceto quando os resultados estão fora do intervalo, você não terá surpresas ao usar ‘+’ para adição, ‘-’ para subtração e ‘*’ para multiplicação.
Cada operador em C tem uma precedência, que é sua posição na ordem gramatical dos vários operadores. Os operadores com a maior precedência capturam os operandos adjacentes primeiro; essas expressões então se tornam operandos para operadores de menor precedência. Damos algumas informações sobre a precedência dos operadores neste capítulo onde descrevemos os operadores; para a explicação completa, veja .
Os operadores aritméticos sempre promovem seus operandos antes de operar sobre eles. Isso significa converter tipos de dados inteiros estreitos para um tipo de dado mais amplo (veja ). Se você está apenas começando a aprender C, não se preocupe com isso ainda.
Dado dois operandos que têm tipos diferentes, a maioria das operações aritméticas os converte para um tipo comum. Por exemplo, se um dado é int
e o outro é double
, o tipo comum é double
. (Isso ocorre porque double
pode representar todos os valores que um int
pode conter, mas o contrário não é verdadeiro.) Para mais detalhes, veja .
Cada uma das operações aritméticas básicas em C tem duas variantes para inteiros: com e sem sinal. A escolha é determinada pelos tipos de dados de seus operandos.
Cada tipo de dado inteiro em C é ou signed (com sinal) ou unsigned (sem sinal). Um tipo com sinal pode conter um intervalo de números positivos e negativos, com o zero próximo ao meio do intervalo. Um tipo sem sinal pode conter apenas números não negativos; seu intervalo começa em zero e vai para cima.
Os tipos inteiros mais básicos são int
, que normalmente pode conter números de -2.147.483.648 a 2.147.483.647, e o unsigned int
, que normalmente pode conter números de 0 a 4.294.967.295. (Isso assume que int
tem 32 bits de largura, o que é sempre verdadeiro para GNU C em computadores reais, mas nem sempre em controladores embarcados.) Veja , para informações completas sobre tipos inteiros.
Quando uma operação aritmética básica recebe dois operandos com sinal, ela realiza aritmética com sinal. Quando recebe dois operandos sem sinal, realiza aritmética sem sinal.
Se um operando for unsigned int
e o outro int
, o operador trata ambos como sem sinal. De maneira mais geral, o tipo comum dos operandos determina se a operação é sinalizada ou não. .
Imprimir os resultados de uma aritmética sem sinal com printf
usando ‘%d’ pode produzir resultados surpreendentes para valores distantes de zero. Mesmo que as regras acima digam que a computação foi feita com aritmética sem sinal, o resultado impresso na tela pode parecer com sinal!
A explicação é que o padrão de bits resultante de uma adição, subtração ou multiplicação é, na verdade, o mesmo para operações com e sem sinal. A diferença está apenas no tipo de dado do resultado, que afeta a interpretação do padrão de bits resultante e se a operação aritmética pode ou não causar um estouro (veja a próxima seção).
Mas ‘%d’ não sabe o tipo de dado de seu argumento. Ele vê apenas o padrão de bits do valor e está definido para interpretá-lo como signed int
. Para imprimi-lo como sem sinal, é necessário usar ‘%u’ em vez de ‘%d’. Veja The GNU C Library no The GNU C Library Reference Manual (disponível somente em inglês).
A aritmética em C nunca opera diretamente em tipos inteiros estreitos (aqueles com menos bits que int
; veja ). Em vez disso, ela os "promove" para int
. Veja .
A aritmética básica em C é feita com os operadores binários usuais da álgebra: adição (‘+’), subtração (‘-’), multiplicação (‘*’) e divisão (‘/’). O operador unário ‘-’ é usado para mudar o sinal de um número. O operador unário +
também existe; ele retorna seu operando inalterado.
‘/’ é o operador de divisão, mas dividir inteiros pode não dar o resultado que você espera. Seu valor é um inteiro, que não é igual ao quociente matemático quando este é uma fração. Use ‘%’ para obter o resto inteiro correspondente, quando necessário. Veja . A divisão de ponto flutuante produz um valor o mais próximo possível do quociente matemático.
Esses operadores usam a sintaxe algébrica com a regra de precedência algébrica usual (veja ) de que multiplicação e divisão são feitas antes de adição e subtração, mas você pode usar parênteses para especificar explicitamente como os operadores devem se aninhar. Eles são associativos à esquerda (veja). Assim,
é equivalente a
Misturar inteiros e números de ponto flutuante em uma operação aritmética básica converte automaticamente os inteiros para ponto flutuante. Na maioria dos casos, isso dá exatamente os resultados desejados. Mas, às vezes, importa exatamente onde a conversão ocorre.
Se i
e j
são inteiros, (i + j) * 2.0
os adiciona como inteiros e, em seguida, converte a soma para ponto flutuante para a multiplicação. Se a adição causar um estouro, isso não é equivalente a converter cada inteiro para ponto flutuante e, em seguida, somar os dois números de ponto flutuante. Você pode obter este último resultado convertendo explicitamente os inteiros, como em ((double) i + (double) j) * 2.0
. Veja .
Somar ou multiplicar vários valores, incluindo alguns inteiros e alguns de ponto flutuante, realiza as operações da esquerda para a direita. Assim, 3.0 + i + j
converte i
para ponto flutuante, depois adiciona 3.0
, depois converte j
para ponto flutuante e adiciona isso. Você pode especificar uma ordem diferente usando parênteses: 3.0 + (i + j)
soma i
e j
primeiro e depois adiciona essa soma (convertida para ponto flutuante) a 3.0
. Nesse aspecto, C difere de outras linguagens, como Fortran.
Você pode usar os operadores de deslocamento para diversos hacks (soluções engenhosas) úteis. Por exemplo, dada uma data especificada pelo dia do mês d
, mês m
e ano y
, você pode armazenar toda a data em um único inteiro date
:
Para extrair o dia, mês e ano de date
, use uma combinação de deslocamento e resto:
-1 << LOWBITS
é uma maneira inteligente de criar um inteiro cujos LOWBITS
bits menos significativos são todos 0 e o restante são todos 1. -(1 << LOWBITS)
é equivalente a isso, devido à associatividade da multiplicação, já que negar um valor é equivalente a multiplicá-lo por -1.
Existem dois tipos de operadores de comparação: os de igualdade e os de ordenação. Comparações de igualdade testam se duas expressões têm o mesmo valor. O resultado é um valor verdade: um número que é 1 para "verdadeiro" ou 0 para "falso".
A comparação de igualdade é escrita como ==
porque =
simples é o operador de atribuição.
Comparações de ordenação testam qual operando é maior ou menor. Seus resultados são valores verdade. Estas são as comparações de ordenação em C:
Operadores bit-a-bit (bitwise, em inglês) operam em inteiros, tratando cada bit de forma independente. Eles não são permitidos para tipos de ponto flutuante.
Operador unário para negação bitwise; este operador altera cada bit de a
de 1 para 0 ou de 0 para 1.
É útil lembrar que ~x + 1
é igual a -x
, para inteiros, e ~x
é igual a -x - 1
. O último exemplo acima mostra isso com -1
como x
.
Operador binário para “and” bit-a-bit ou “conjunção”. Cada bit no resultado é 1 se esse bit for 1 em ambos a
e b
.
Operador binário para “ou” bit-a-bit (“ou inclusivo” ou “disjunção”). Cada bit no resultado é 1 se esse bit for 1 em a
ou b
.
Operador binário para “xor” bit-a-bit (“ou exclusivo”). Cada bit no resultado é 1 se esse bit for 1 em exatamente um de a
e b
.
Aviso: C define uma ordem de precedência para os operadores binários bit-a-bit, mas você nunca deve confiar nisso. Você nunca deve confiar em como os operadores binários bit-a-bit se relacionam em precedência com os operadores binários aritméticos e de deslocamento. Outros programadores não lembram dessa ordem de precedência, então sempre use parênteses para especificar explicitamente o aninhamento.
Por exemplo, suponha que offset
seja um inteiro que especifica o deslocamento dentro da memória compartilhada de uma tabela, exceto que seus poucos bits inferiores (LOWBITS indica quantos) são flags especiais. Veja como obter apenas esse deslocamento e adicioná-lo ao endereço base.
Graças ao conjunto externo de parênteses, não precisamos saber se ‘&’ tem precedência mais alta que ‘+’. Graças ao conjunto interno, não precisamos saber se ‘&’ tem precedência mais alta que ‘<<’. Mas podemos confiar que todos os operadores unários têm precedência mais alta que qualquer operador binário, então não precisamos de parênteses ao redor do operando esquerdo de ‘<<’.
Deslocar um inteiro significa mover os valores dos bits para a esquerda ou para a direita dentro dos bits do tipo de dado. O deslocamento é definido apenas para inteiros. Aqui está a forma de escrever:
Aviso: Se a contagem de deslocamento for maior ou igual à largura em bits do primeiro operando promovido, os resultados dependem da máquina. Logicamente falando, o valor "correto" seria -1 (para deslocamento à direita de um número negativo) ou 0 (em todos os outros casos), mas o resultado real é o que a instrução de deslocamento da máquina faz nesse caso. Portanto, a menos que você possa provar que o segundo operando não é grande demais, escreva código para verificá-lo em tempo de execução.
Aviso: Nunca confie na relação entre os operadores de deslocamento e outros operadores binários aritméticos em termos de precedência. Programadores não lembram dessas precedências e não entenderão o código. Sempre use parênteses para especificar explicitamente o aninhamento, assim:
Nota: De acordo com o padrão C, o deslocamento de valores com sinal não é garantido para funcionar corretamente quando o valor deslocado é negativo ou se torna negativo durante a operação de deslocamento à esquerda. No entanto, apenas as pessoas mais rigorosas teriam motivo para se preocupar com isso; apenas computadores com instruções de deslocamento estranhas poderiam, plausivelmente, fazer isso de forma incorreta. No GNU C, a operação sempre funciona como esperado.
A divisão de inteiros em C arredonda o resultado para um número inteiro. O resultado é sempre arredondado em direção a zero.
Para obter o resto correspondente, use o operador ‘%’:
‘%’ tem a mesma precedência de operador que ‘/’ e ‘*’.
A partir do quociente arredondado e do resto, você pode reconstruir o dividendo, assim:
Para fazer uma divisão não arredondada, use ponto flutuante. Se apenas um operando for de ponto flutuante, ‘/’ converte o outro operando para ponto flutuante.
O operador de resto ‘%’ não é permitido para operandos de ponto flutuante, porque não é necessário. O conceito de resto faz sentido para inteiros porque o resultado da divisão de inteiros deve ser um número inteiro. Para ponto flutuante, o resultado da divisão é um número de ponto flutuante, ou seja, uma fração, que diferirá do resultado exato apenas por uma quantidade muito pequena.
A divisão por zero leva a resultados imprevisíveis—dependendo do tipo de computador, pode causar um sinal SIGFPE ou pode produzir um resultado numérico.
Atenção: Certifique-se de que o programa não divide por zero. Se você não puder garantir que o divisor não é zero, teste se ele é zero e pule a divisão, se for o caso.
Para quaisquer inteiros a
e b
, exatamente uma das comparações a < b
, a == b
e a > b
é verdadeira, assim como na matemática. No entanto, se a
e b
são valores especiais de ponto flutuante (não números ordinários), todas as três podem ser falsas. Veja e .
Os exemplos nesta seção usam constantes binárias, começando com ‘0b’ (veja ). Elas representam inteiros de 32 bits do tipo int
. Vamos aos operadores:
Para entender o efeito desses operadores em inteiros com sinal, lembre-se de que todos os computadores modernos usam representação em complemento de dois (veja ) para inteiros negativos. Isso significa que o bit mais alto do número indica o sinal; ele é 1 para um número negativo e 0 para um número positivo. Em um número negativo, o valor nos outros bits aumenta à medida que o número se aproxima de zero, de modo que 0b111…111
é -1 e 0b100…000
é o inteiro negativo mais baixo possível.
O operando à esquerda é o valor a ser deslocado, e o operando à direita indica quantos bits deslocá-lo (a contagem de deslocamento). O operando à esquerda é promovido (veja ), então o deslocamento nunca opera em um tipo de inteiro estreito; é sempre int
ou mais amplo. O resultado da operação de deslocamento tem o mesmo tipo que o operando esquerdo promovido.
Existem funções na biblioteca padrão C para calcular restos a partir da divisão de números de ponto flutuante por valores inteiros. Veja no The GNU C Library Reference Manual (disponível somente em inglês).
A divisão de inteiros causa estouro em um caso específico: dividir o menor valor negativo para o tipo de dado (veja ) por -1. Isso ocorre porque o resultado correto, que é o número positivo correspondente, não cabe (veja ) no mesmo número de bits. Em alguns computadores atualmente em uso, isso sempre causa um sinal SIGFPE (veja ), o mesmo comportamento que a opção -ftrapv especifica (veja ).
Uma operação de deslocamento move os bits para uma extremidade do número e precisa gerar novos bits na outra extremidade.
Deslocar para a esquerda um bit deve gerar um novo bit menos significativo. Ele sempre insere um zero ali. Isso é equivalente a multiplicar pela potência de 2 correspondente. Por exemplo,
O significado de deslocar para a direita depende se o tipo de dado é com sinal ou sem sinal (veja ). Para um tipo de dado com sinal, ele realiza um “deslocamento aritmético,” que mantém o sinal do número inalterado ao duplicar o bit de sinal. Para um tipo de dado sem sinal, ele realiza um “deslocamento lógico,” que sempre insere zeros no bit mais significativo.
Em ambos os casos, deslocar para a direita um bit é uma divisão por dois, arredondando em direção ao infinito negativo. Por exemplo,
Para um operando esquerdo negativo a
, a >> 1
não é equivalente a a / 2
. Ambos dividem por 2, mas ‘/’
arredonda em direção a zero.
A contagem de deslocamento deve ser zero ou maior. Deslocar por um número negativo de bits produz resultados dependentes da máquina.