¿Por qué unsigned int 0xFFFFFFFF es igual a int -1?

En C o C ++ se dice que el número máximo que puede contener un tamaño_t (un tipo de datos int sin firmar) es el mismo que el de lanzar -1 a ese tipo de datos. por ejemplo, consulte Valor inválido para size_t

¿Por qué?

Quiero decir, (hablando de entradas de 32 bits) AFAIK, el bit más significativo contiene el signo en un tipo de datos con signo (es decir, el bit 0x80000000 para formar un número negativo). entonces, 1 es 0x00000001 .. 0x7FFFFFFFF es el mayor número positivo que puede contener un tipo de datos int.

Entonces, AFAIK la representación binaria de -1 int debería ser 0x80000001 (quizás estoy equivocado). ¿por qué / cómo este valor binario se convierte a algo completamente diferente (0xFFFFFFFF) al convertir ints a unsigned? o … ¿cómo es posible formar un binario -1 de 0xFFFFFFFF?

No tengo dudas de que en C: ((unsigned int) -1) == 0xFFFFFFFF o ((int) 0xFFFFFFFF) == -1 es igualmente cierto que 1 + 1 == 2, me pregunto por qué.

C y C ++ pueden ejecutarse en muchas architectures diferentes y tipos de máquinas. En consecuencia, pueden tener diferentes representaciones de números: complemento de Dos, y el complemento de Uno es el más común. En general, no debe confiar en una representación particular en su progtwig.

Para tipos enteros sin signo ( size_t es uno de ellos), el estándar C (y el estándar C ++ también, creo) especifica reglas de desbordamiento precisas. En resumen, si SIZE_MAX es el valor máximo del tipo size_t , entonces la expresión

(size_t) (SIZE_MAX + 1)

se garantiza que es 0 , y por lo tanto, puede estar seguro de que (size_t) -1 es igual a SIZE_MAX . Lo mismo es cierto para otros tipos sin firmar.

Tenga en cuenta que lo anterior es cierto:

  • para todos los tipos sin firmar,
  • incluso si la máquina subyacente no representa los números en complemento a Dos . En este caso, el comstackdor debe asegurarse de que la identidad sea verdadera.

Además, lo anterior significa que no puede confiar en representaciones específicas para tipos firmados .

Editar : para responder algunos de los comentarios:

Digamos que tenemos un fragmento de código como:

 int i = -1; long j = i; 

Hay una conversión de tipo en la asignación a j . Suponiendo que int y long tienen diferentes tamaños (la mayoría de los sistemas de 64 bits), los patrones de bits en las ubicaciones de memoria para i y j van a ser diferentes, porque tienen diferentes tamaños. El comstackdor se asegura de que los valores de i y j sean -1 .

Del mismo modo, cuando lo hacemos:

 size_t s = (size_t) -1 

Hay una conversión de tipo en curso. El -1 es de tipo int . Tiene un patrón de bits, pero eso es irrelevante para este ejemplo porque cuando la conversión a size_t tiene lugar debido a la conversión, el comstackdor traducirá el valor de acuerdo con las reglas para el tipo ( size_t en este caso). Por lo tanto, incluso si int y size_t tienen diferentes tamaños, el estándar garantiza que el valor almacenado en s arriba será el valor máximo que size_t puede tomar.

Si lo hacemos:

 long j = LONG_MAX; int i = j; 

Si LONG_MAX es mayor que INT_MAX , entonces el valor en i está definido por la implementación (C89, sección 3.2.1.2).

Se llama complemento de dos. Para hacer un número negativo, invierta todos los bits y luego agregue 1. Por lo tanto, para convertir 1 a -1, inviértalo en 0xFFFFFFFE, luego agregue 1 para hacer 0xFFFFFFFF.

En cuanto a por qué se hace de esta manera, Wikipedia dice:

El sistema de dos complementos tiene la ventaja de no requerir que los circuitos de sum y resta examinen los signos de los operandos para determinar si sumr o restar. Esta propiedad hace que el sistema sea más simple de implementar y capaz de manejar fácilmente una aritmética de mayor precisión.

Su primera pregunta, acerca de por qué (unsigned)-1 da el mayor valor posible sin signo, solo se relaciona accidentalmente con el complemento a dos. La razón -1 para un tipo sin signo da el mayor valor posible para ese tipo es porque el estándar dice que los tipos sin signo “siguen las leyes del módulo aritmético 2 n donde n es el número de bits en la representación del valor de ese tamaño particular de entero.”

Ahora, para el complemento a 2, la representación del mayor valor sin signo posible y -1 resulta ser el mismo, pero incluso si el hardware usa otra representación (por ejemplo, complemento de 1 o signo / magnitud), la conversión de -1 a un tipo sin signo debe todavía producen el mayor valor posible para ese tipo.

El complemento de dos es muy bueno para hacer restas como agregar 🙂

     11111110 (254 o -2)
    +00000001 (1)
    ---------
     11111111 (255 o -1)

     11111111 (255 o -1) 
    +00000001 (1)
    ---------
    100000000 (0 + 256)

Esa es la encoding complementaria de dos .

La principal ventaja es que obtienes la misma encoding si estás utilizando un int sin firmar o firmado. Si resta 1 de 0, el entero simplemente se envuelve. Por lo tanto, 1 menos que 0 es 0xFFFFFFFF.

Porque el patrón de bits para un int -1 es FFFFFFFF en hexadecimal sin signo. 11111111111111111111111111111111 binario sin firmar. Pero en int el primer bit indica si es negativo. Pero en unsigned int el primer bit es solo un número extra porque un int sin signo no puede ser negativo. Entonces, el bit extra hace que una int sin firmar pueda almacenar números mayores. Al igual que con unsigned int 11111111111111111111111111111111 (binario) o FFFFFFFF (hexadecimal) es el número más grande que un uint puede almacenar. Los Ints sin firmar no son recomendables porque si son negativos, se desborda y llega al número más grande.