novedades - new features c#
Tipo de literales enteros y ~ en C (4)
Soy un principiante de C, y estoy confundido por el siguiente ejemplo que se encuentra en el libro de respuestas de C.
Una forma de encontrar el tamaño del largo sin firmar en su sistema es escribir:
printf("%llu", (unsigned long long) ~0);
No tengo idea de por qué funciona esta sintaxis?
En mi sistema, int
son 32 bits y long long
son 64 bits.
Lo que esperaba era que, dado que 0
es una constante de tipo entero, ~0
calcula la negación de un entero de 32 bits, que luego se convierte en un unsigned long long
por el operador de conversión. Esto debería dar 2 32 - 1 como resultado.
De alguna manera, parece que el operador ~
ya sabe que debería actuar en 64 bits?
¿El compilador interpreta esta instrucción como printf("%llu", ~(unsigned long long)0);
? Eso no suena bien ya que el elenco y ~
tienen la misma prioridad.
De alguna manera, parece que el operador ~ ya sabe que debería actuar en 64 bits?
No es el operador ~
, es el elenco. Aquí es cómo se realiza la conversión de enteros de acuerdo con el estándar:
6.3.1.3 Enteros firmados y sin firmar
- Cuando un valor con tipo entero se convierte en otro tipo entero distinto de _Bool, si el valor puede representarse por el nuevo tipo, no se modifica.
- De lo contrario, si el nuevo tipo no tiene firma, el valor se convierte sumando o restando repetidamente uno más que el valor máximo que se puede representar en el nuevo tipo hasta que el valor esté en el rango del nuevo tipo.
- De lo contrario, el nuevo tipo está firmado y el valor no puede representarse en él; o bien el resultado está definido por la implementación o se genera una señal definida por la implementación.
El valor de int
~0
firmado corresponde a -1
en los sistemas con una representación complementaria de dos de valores negativos. No se puede representar con un unsigned long long
, por lo que el primer punto no se aplica.
El segundo punto no se aplica: el nuevo tipo no está firmado, por lo que MAX
de unsigned long long
se agrega a -1
una vez para obtener el resultado en el rango de unsigned long long
. Esto tiene el mismo efecto que la extensión de signo de -1
a 64 bits.
Según el borrador del comité N1570:
6.5.3.3 Operadores aritméticos unarios
- El resultado del operador ~ es el complemento a nivel de bits de su operando (promovido) (es decir, cada bit en el resultado se establece solo si no se establece el bit correspondiente en el operando convertido). Las promociones enteras se realizan en el operando, y el resultado tiene el tipo promovido. Si el tipo promovido es un " tipo sin signo , la expresión ~ E es equivalente al valor máximo representable en ese tipo menos E".
§6.2.6.2 Idioma 45 :
(complemento de uno). ¿Cuál de estos se aplica a la implementación de fi nida , como si el valor con bit de signo 1 y todos los bits de valor cero (para los dos primeros), o con bit de signo y todos los bits de valor 1 (para el complemento de unos), es una representación de trampa? o un valor normal. En el caso del signo y la magnitud y el complemento de unos, si esta representación es un valor normal, se llama cero negativo.
De ahí, el comportamiento del código:
printf("%llu", (unsigned long long) ~0);
En algunas máquinas, la implementación se define y no se define, no de acuerdo con lo esperado, depende de las representaciones internas de los enteros en la máquina.
Y de acuerdo con la sección 6.5.3.3, la forma aprobada para escribir código sería:
printf("%llu", (unsigned long long) ~0u);
Además, el tipo de ~0u
es unsigned int, mientras que lo está llu
en unsigned long long int
para el que la cadena de formato es llu
. Para imprimir ~0u
usando la cadena de formato %u
.
Para aprender el concepto básico de conversión de tipos, puede leer: ¿Qué es exactamente una conversión de tipos en C / C ++?
0
es del tipo int
, no unsigned int
. Por lo tanto, ~0
(en las máquinas que usan la representación de enteros de complemento de dos, que es todo lo que se usa hoy) será -1, no 2 32 - 1.
Suponiendo que un unsigned long long
64 bits unsigned long long
, (unsigned long long) -1
es -1
módulo 2 64 , que es 2 64 - 1.
0
es un int
~0
sigue siendo un int
, es decir, el valor -1
.
Lanzar un int
a unsigned long long
es simplemente para que coincida con el tipo que printf espera con la conversión llu
.
Sin embargo, el valor de -1 extendido por un largo sin firmar largo debe ser 0xffffffff para 4 bytes int y 0xffffffffffffffff para 8 bytes int.