Pergunta

Suponha que eu tenha o seguinte código C.

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

Quais conversões implícitas estão acontecendo aqui e este código é seguro para todos os valores de u e i?(Seguro, no sentido de que mesmo que resultado neste exemplo irá transbordar para um grande número positivo, eu poderia convertê-lo de volta para um interno e obtenha o resultado real.)

Foi útil?

Solução

Resposta curta

Seu i vai ser convertido para um número inteiro sem sinal adicionando UINT_MAX + 1, então a adição será realizada com os valores sem sinal, resultando em um grande result (dependendo dos valores de u e i).

Resposta longa

De acordo com o padrão C99:

6.3.1.8 Conversões aritméticas usuais

  1. Se ambos os operandos tiverem o mesmo tipo, nenhuma conversão adicional será necessária.
  2. Caso contrário, se ambos os operandos tiverem tipos inteiros com sinal ou ambos tiverem tipos inteiros sem sinal, o operando com o tipo de classificação de conversão de número inteiro menor será convertido para o tipo do operando com classificação maior.
  3. Caso contrário, se o operando que possui tipo inteiro sem sinal tiver rank maior ou igual ao rank do tipo do outro operando, então o operando com tipo inteiro com sinal é convertido para o tipo do operando com tipo inteiro sem sinal.
  4. Caso contrário, se o tipo do operando com tipo inteiro com sinal puder representar todos os valores do tipo do operando com tipo inteiro sem sinal, então o operando com tipo inteiro sem sinal será convertido para o tipo do operando com tipo inteiro com sinal.
  5. Caso contrário, ambos os operandos são convertidos para o tipo inteiro sem sinal correspondente ao tipo do operando com tipo inteiro com sinal.

No seu caso, temos um unsigned int (u) e assinado int (i).Referindo-se a (3) acima, como ambos os operandos têm a mesma classificação, seu i precisará ser convertido para um número inteiro sem sinal.

6.3.1.3 Inteiros assinados e não assinados

  1. Quando um valor com tipo inteiro é convertido para outro tipo inteiro diferente de _Bool, se o valor puder ser representado pelo novo tipo, ele permanecerá inalterado.
  2. Caso contrário, se o novo tipo não tiver sinal, o valor será convertido adicionando ou subtraindo repetidamente um valor a mais que o valor máximo que pode ser representado no novo tipo, até que o valor esteja no intervalo do novo tipo.
  3. Caso contrário, o novo tipo é assinado e o valor não pode ser representado nele;ou o resultado é definido pela implementação ou um sinal definido pela implementação é gerado.

Agora precisamos nos referir a (2) acima.Seu i será convertido em um valor sem sinal adicionando UINT_MAX + 1.Então o resultado vai depender de como UINT_MAX é definido em sua implementação.Será grande, mas não transbordará, porque:

6.2.5 (9)

Uma computação envolvendo operandos sem sinal nunca pode estourar, porque um resultado que não pode ser representado pelo tipo inteiro sem sinal resultante é reduzido ao módulo pelo número que é um maior que o maior valor que pode ser representado pelo tipo resultante.

Bônus:Conversão Aritmética Semi-WTF

#include <stdio.h>

int main(void)
{
  unsigned int plus_one = 1;
  int minus_one = -1;

  if(plus_one < minus_one)
    printf("1 < -1");
  else
    printf("boring");

  return 0;
}

Você pode usar este link para tentar fazer isso online: https://repl.it/repls/QuickWhimsicalBytes

Bônus:Efeito colateral da conversão aritmética

Regras de conversão aritmética podem ser usadas para obter o valor de UINT_MAX inicializando um valor não assinado para -1, ou seja:

unsigned int umax = -1; // umax set to UINT_MAX

É garantido que isso seja portátil, independentemente da representação do número assinado do sistema devido às regras de conversão descritas acima.Veja esta pergunta SO para obter mais informações: É seguro usar -1 para definir todos os bits como verdadeiros?

Outras dicas

Conversão de assinado para não assinado não necessariamente apenas copie ou reinterprete a representação do valor assinado.Citando o padrão C (C99 6.3.1.3):

Quando um valor com tipo inteiro é convertido em outro tipo de número inteiro que não seja _bool, se o valor puder ser representado pelo novo tipo, ele não é alterado.

Caso contrário, se o novo tipo não for assinado, o valor será convertido adicionando ou subtraindo repetidamente um a mais que o valor máximo que pode ser representado no novo tipo até que o valor esteja no intervalo do novo tipo.

Caso contrário, o novo tipo é assinado e o valor não pode ser representado nele;O resultado é definido por implementação ou um sinal definido por implementação é aumentado.

Para a representação do complemento de dois, que é quase universal hoje em dia, as regras correspondem à reinterpretação dos bits.Mas para outras representações (sinal e magnitude ou complemento de uns), a implementação C ainda deve providenciar o mesmo resultado, o que significa que a conversão não pode apenas copiar os bits.Por exemplo, (unsigned)-1 == UINT_MAX, independentemente da representação.

Em geral, as conversões em C são definidas para operar em valores, não em representações.

Para responder à pergunta original:

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

O valor de i é convertido em unsigned int, produzindo UINT_MAX + 1 - 5678.Este valor é então adicionado ao valor não assinado 1234, produzindo UINT_MAX + 1 - 4444.

(Ao contrário do estouro não assinado, o estouro assinado invoca um comportamento indefinido.Wraparound é comum, mas não é garantido pelo padrão C - e as otimizações do compilador podem causar estragos no código que faz suposições injustificadas.)

Referindo-se a a Bíblia:

  • Sua operação de adição faz com que o int seja convertido em um int não assinado.
  • Assumindo representação em complemento de dois e tipos de tamanhos iguais, o padrão de bits não muda.
  • A conversão de unsigned int para Signed Int depende da implementação.(Mas provavelmente funciona da maneira que você espera na maioria das plataformas atualmente.)
  • As regras são um pouco mais complicadas no caso de combinar sinais e não assinados de tamanhos diferentes.

Quando uma variável não assinada e uma variável assinada são adicionadas (ou qualquer operação binária), ambas são implicitamente convertidas em não assinadas, o que neste caso resultaria em um resultado enorme.

Portanto, é seguro no sentido de que o resultado pode ser enorme e errado, mas nunca irá falhar.

Ao converter de assinado para não assinado, existem duas possibilidades.Os números que eram originalmente positivos permanecem (ou são interpretados como) com o mesmo valor.Os números que eram originalmente negativos agora serão interpretados como números positivos maiores.

Como foi respondido anteriormente, você pode alternar entre assinado e não assinado sem problemas.O limite para inteiros assinados é -1 (0xFFFFFFFF).Tente somar e subtrair isso e você descobrirá que pode retroceder e acertar.

No entanto, se você for fazer conversões de um lado para o outro, recomendo fortemente nomear suas variáveis ​​de forma que fique claro de que tipo elas são, por exemplo:

int iValue, iResult;
unsigned int uValue, uResult;

É muito fácil se distrair com questões mais importantes e esquecer qual variável é de que tipo, se elas forem nomeadas sem nenhuma dica.Você não deseja converter para um não assinado e usá-lo como um índice de array.

Que conversões implícitas estão acontecendo aqui,

serei convertido em um número inteiro sem sinal.

e esse código é seguro para todos os valores de você e eu?

Seguro no sentido de estar bem definido sim (ver https://stackoverflow.com/a/50632/5083516 ).

As regras são escritas em linguagem padrão normalmente difícil de ler, mas essencialmente qualquer representação usada no inteiro assinado, o inteiro não assinado conterá uma representação do número em complemento de 2.

Adição, subtração e multiplicação funcionarão corretamente nesses números, resultando em outro número inteiro sem sinal contendo um número em complemento de dois representando o "resultado real".

a divisão e a conversão para tipos inteiros sem sinal maiores terão resultados bem definidos, mas esses resultados não serão representações em complemento de 2 do "resultado real".

(Seguro, no sentido de que mesmo que o resultado neste exemplo exceda um grande número positivo, eu poderia convertê-lo de volta em um int e obter o resultado real.)

Embora as conversões de assinado para não assinado sejam definidas pelo padrão, o inverso é definido pela implementação, tanto gcc quanto msvc definem a conversão de forma que você obtenha o "resultado real" ao converter um número de complemento de 2 armazenado em um número inteiro não assinado de volta para um número inteiro assinado .Espero que você encontre qualquer outro comportamento apenas em sistemas obscuros que não usam complemento de 2 para números inteiros assinados.

https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx

Muitas respostas horríveis

Ozgur Ozcitak

Quando você lançou de assinado para não assinado (e vice -versa), a representação interna do número não muda.O que muda é como o compilador interpreta o bit de sinal.

Isso está completamente errado.

Mats Fredriksson

Quando uma variável não assinada e uma assinada são adicionadas (ou qualquer operação binária) ambas são implicitamente convertidas em não assinadas, o que, neste caso, resultaria em um enorme resultado.

Isso também está errado.Ints não assinados podem ser promovidos a ints caso tenham precisão igual devido aos bits de preenchimento no tipo não assinado.

balançar a cabeça

Sua operação de adição faz com que o int seja convertido em um Int não assinado.

Errado.Talvez sim e talvez não.

A conversão de INT não assinada para Int assinada depende da implementação.(Mas provavelmente funciona da maneira que você espera na maioria das plataformas hoje em dia.)

Errado.É um comportamento indefinido se causar estouro ou se o valor for preservado.

Anônimo

O valor de I é convertido em não assinado int ...

Errado.Depende da precisão de um int em relação a um int não assinado.

Preço Taylor

Como foi respondido anteriormente, você pode ser lançado entre assinado e não assinado sem um problema.

Errado.Tentar armazenar um valor fora do intervalo de um número inteiro assinado resulta em comportamento indefinido.

Agora posso finalmente responder à pergunta.

Caso a precisão de int seja igual a unsigned int, u será promovido a um int assinado e você obterá o valor -4444 da expressão (u+i).Agora, se você e eu tivermos outros valores, você poderá obter um comportamento excessivo e indefinido, mas com esses números exatos você obterá -4444 [1].Este valor terá o tipo int.Mas você está tentando armazenar esse valor em um unsigned int para que ele seja convertido em um unsigned int e o valor que o resultado acabará tendo seria (UINT_MAX+1) - 4444.

Caso a precisão de unsigned int seja maior que a de um int, o int assinado será promovido para um int não assinado produzindo o valor (UINT_MAX+1) - 5678 que será adicionado ao outro int não assinado 1234.Se você e eu tiverem outros valores, que façam a expressão ficar fora do intervalo {0..UINT_MAX}, o valor (UINT_MAX+1) será adicionado ou subtraído até que o resultado caia dentro do intervalo {0..UINT_MAX) e nenhum comportamento indefinido ocorrerá.

O que é precisão?

Os números inteiros possuem bits de preenchimento, bits de sinal e bits de valor.Obviamente, inteiros não assinados não possuem um bit de sinal.É ainda garantido que o caractere não assinado não tenha bits de preenchimento.O número de bits de valores que um inteiro possui é quanta precisão ele possui.

[Pegadinhas]

A macro sizeof sozinha não pode ser usada para determinar a precisão de um número inteiro se bits de preenchimento estiverem presentes.E o tamanho de um byte não precisa ser um octeto (oito bits), conforme definido por C99.

[1] O estouro pode ocorrer em um de dois pontos.Antes da adição (durante a promoção) - quando você tem um unsigned int que é muito grande para caber dentro de um int.O estouro também pode ocorrer após a adição, mesmo que o unsigned int esteja dentro do intervalo de um int; após a adição, o resultado ainda pode estourar.


Por falar nisso, sou um estudante recente de pós-graduação tentando encontrar trabalho;)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top